【问题标题】:How to get number of days between two calendar instance?如何获取两个日历实例之间的天数?
【发布时间】:2013-10-19 05:58:22
【问题描述】:

如果有日期变化,我想找出两个 Calendar 对象之间的天数差异,例如如果时钟从 23:59-0:00 滴答作响,则应该有天数差异。

这是我写的

public static int daysBetween(Calendar startDate, Calendar endDate) {  
    return Math.abs(startDate.get(Calendar.DAY_OF_MONTH)-endDate.get(Calendar.DAY_OF_MONTH));  
} 

但它不起作用,因为如果有月份差异,它只会在天之间产生差异,它毫无价值。

【问题讨论】:

  • 这样的问题是他们写joda time的原因。 Java 日历太没用了……非得用吗?
  • 因为如果必须的话,坦率地说,我会将两者都转换为millis并使用这些:-)

标签: java android date calendar


【解决方案1】:

尝试以下方法:

public static long daysBetween(Calendar startDate, Calendar endDate) {
    long end = endDate.getTimeInMillis();
    long start = startDate.getTimeInMillis();
    return TimeUnit.MILLISECONDS.toDays(Math.abs(end - start));
}

【讨论】:

  • 它可能会增加 1。
  • 这不是原始问题的正确答案。如果一天的差异定义为 86,400,000 毫秒(24 小时内的毫秒数),则此答案是正确的。但是,最初的问题要求if there is date change like If clock ticked from 23:59-0:00 there should be a day difference。因此,问题将 1 天的差异定义为日历日相差 1。
  • 在差值计算之前将小时和分钟设置为0怎么样
【解决方案2】:

在 Java 8 及更高版本中,我们可以简单地使用 java.time 类。

hoursBetween = ChronoUnit.HOURS.between(calendarObj.toInstant(), calendarObj.toInstant());

daysBetween = ChronoUnit.DAYS.between(calendarObj.toInstant(), calendarObj.toInstant());

【讨论】:

  • 也可以在 Java 8 之前使用... java.time 的大部分功能在 ThreeTen-Backport 项目中被反向移植到 Java 6 和 Java 7。在ThreeTenABP 项目中进一步适用于Android。
  • 您的天数代码根据 UTC 计算天数。如果考虑原始Calendar 对象的时区,经过的天数可能会有所不同。更好的方法是使用GregorianCalendar 转换为ZonedDateTime,提取LocalDate,然后计算Period
  • 这不也有将天数计算为“24 小时周期”而不是实际日历天数的问题吗?
【解决方案3】:

此函数将两个日历之间的天数计算为它们之间一个月的日历天数,这是 OP 想要的。计算是通过计算日历之间的 86,400,000 毫秒的倍数在都设置为各自日期的午夜之后的多少来执行的。

例如,我的函数将计算 1 月 1 日晚上 11:59 和 1 月 2 日中午 12:01 的日历之间 1 天的差异。

import java.util.concurrent.TimeUnit;

/**
 * Compute the number of calendar days between two Calendar objects. 
 * The desired value is the number of days of the month between the
 * two Calendars, not the number of milliseconds' worth of days.
 * @param startCal The earlier calendar
 * @param endCal The later calendar
 * @return the number of calendar days of the month between startCal and endCal
 */
public static long calendarDaysBetween(Calendar startCal, Calendar endCal) {

    // Create copies so we don't update the original calendars.

    Calendar start = Calendar.getInstance();
    start.setTimeZone(startCal.getTimeZone());
    start.setTimeInMillis(startCal.getTimeInMillis());

    Calendar end = Calendar.getInstance();
    end.setTimeZone(endCal.getTimeZone());
    end.setTimeInMillis(endCal.getTimeInMillis());

    // Set the copies to be at midnight, but keep the day information.

    start.set(Calendar.HOUR_OF_DAY, 0);
    start.set(Calendar.MINUTE, 0);
    start.set(Calendar.SECOND, 0);
    start.set(Calendar.MILLISECOND, 0);

    end.set(Calendar.HOUR_OF_DAY, 0);
    end.set(Calendar.MINUTE, 0);
    end.set(Calendar.SECOND, 0);
    end.set(Calendar.MILLISECOND, 0);

    // At this point, each calendar is set to midnight on 
    // their respective days. Now use TimeUnit.MILLISECONDS to
    // compute the number of full days between the two of them.

    return TimeUnit.MILLISECONDS.toDays(
            Math.abs(end.getTimeInMillis() - start.getTimeInMillis()));
}

【讨论】:

  • 如果一个日期在夏令时,而另一个不在,我认为这不会起作用。
【解决方案4】:

@JK1 的扩展很好的答案:

public static long daysBetween(Calendar startDate, Calendar endDate) {

    //Make sure we don't change the parameter passed
    Calendar newStart = Calendar.getInstance();
    newStart.setTimeInMillis(startDate.getTimeInMillis());
    newStart.set(Calendar.HOUR_OF_DAY, 0);
    newStart.set(Calendar.MINUTE, 0);
    newStart.set(Calendar.SECOND, 0);
    newStart.set(Calendar.MILLISECOND, 0);

    Calendar newEnd = Calendar.getInstance();
    newEnd.setTimeInMillis(endDate.getTimeInMillis());
    newEnd.set(Calendar.HOUR_OF_DAY, 0);
    newEnd.set(Calendar.MINUTE, 0);
    newEnd.set(Calendar.SECOND, 0);
    newEnd.set(Calendar.MILLISECOND, 0);

    long end = newEnd.getTimeInMillis();
    long start = newStart.getTimeInMillis();
    return TimeUnit.MILLISECONDS.toDays(Math.abs(end - start));
}

【讨论】:

    【解决方案5】:

    更新Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。有关经过时间的计算,请参阅 Anees A 的 the Answer,请参阅我的 new Answer,了解使用 java.time 计算日历的经过天数。

    乔达时间

    旧的 java.util.Date/.Calendar 类是出了名的麻烦,应该避免。

    改为使用Joda-Time 库。除非您拥有 Java 8 技术,在这种情况下使用它的后继者,即内置的 java.time 框架(截至 2015 年在 Android 中没有)。

    由于您只关心定义为日期的“天”(不是 24 小时周期),因此让我们关注日期。 Joda-Time 提供 LocalDate 类来表示没有时间和时区的仅日期值。

    虽然缺少时区,但请注意时区在确定“今天”等日期时至关重要。新的一天在东方比西方更早地黎明。所以世界各地的日期在某一时刻是不一样的,日期取决于你所在的时区。

    DateTimeZone zone = DateTimeZone.forID ( "America/Montreal" );
    LocalDate today = LocalDate.now ( zone );
    

    让我们数一下距离下周还有多少天,当然应该是七天。

    LocalDate weekLater = today.plusWeeks ( 1 );
    int elapsed = Days.daysBetween ( today , weekLater ).getDays ();
    

    最后的getDaysdaysBetween 返回的Days 对象中提取一个普通的int 数字。

    转储到控制台。

    System.out.println ( "today: " + today + " to weekLater: " + weekLater + " is days: " + days );
    

    今天:2015-12-22 到 weekLater:2015-12-29 是天:7

    您有日历对象。我们需要将它们转换为 Joda-Time 对象。在内部,Calendar 对象有一个 long 整数,用于跟踪自 UTC 中 1970 年第一个时刻的纪元以来的毫秒数。我们可以提取该数字,并将其提供给 Joda-Time。我们还需要指定我们打算用来确定日期的所需时区。

    long startMillis = myStartCalendar.getTimeInMillis();
    DateTime startDateTime = new DateTime( startMillis , zone );
    
    long stopMillis = myStopCalendar.getTimeInMillis();
    DateTime stopDateTime = new DateTime( stopMillis , zone );
    

    从 DateTime 对象转换为 LocalDate。

    LocalDate start = startDateTime.toLocalDate();
    LocalDate stop = stopDateTime.toLocalDate();
    

    现在执行我们之前看到的相同经过的计算。

    int elapsed = Days.daysBetween ( start , stop ).getDays ();
    

    【讨论】:

      【解决方案6】:

      tl;博士

      java.time.temporal.ChronoUnit         // The java.time classes are built into Java 8+ and Android 26+. For earlier Android, get must of the functionality by using the latest tooling with "API desugaring".
      .DAYS                                 // A pre-defined enum object.
      .between(
          ( (GregorianCalendar) startCal )  // Cast from the more abstract `Calendar` to the more concrete `GregorianCalendar`. 
          .toZonedDateTime()                // Convert from legacy class to modern class. Returns a `ZonedDateTime` object.
          .toLocalDate()                    // Extract just the date, to get the Question's desired whole-days count, ignoring fractional days. Returns a `LocalDate` object.
          , 
          ( (GregorianCalendar) endCal )
          .toZonedDateTime()
          .toLocalDate()
      )                                     // Returns a number of days elapsed between our pair of `LocalDate` objects. 
      

      java.time

      Answer by Mohamed Anees A 在几个小时内是正确的,但在几天内是错误的。计算天数需要time zone。另一个答案使用Instant,这是UTC的时刻,总是在UTC。所以您没有得到正确的日历天数。

      要按日历计算天数,请将旧版 Calendar 转换为 ZonedDateTime,然后输入到 ChronoUnit.DAYS.between

      时区

      时区对于确定日期至关重要。对于任何给定的时刻,日期在全球范围内因区域而异。例如,Paris France 中午夜后几分钟是新的一天,而 Montréal Québec 中仍然是“昨天”。

      如果没有指定时区,JVM 会隐式应用其当前的默认时区。该默认值可能在运行时(!)期间change at any moment,因此您的结果可能会有所不同。最好将您想要/预期的时区明确指定为参数。如果关键,请与您的用户确认该区域。

      Continent/Region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 2-4 个字母的缩写,例如 ESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的(!)。

      ZoneId z = ZoneId.of( "America/Montreal" ) ;  
      LocalDate today = LocalDate.now( z ) ;           // Capture the current date as seen through the wall-clock time used by the people of a certain region (a time zone).
      

      如果你想使用 JVM 当前的默认时区,请求它并作为参数传递。如果省略,代码会变得模糊,因为我们不确定您是否打算使用默认值,或者您是否像许多程序员一样没有意识到这个问题。

      ZoneId z = ZoneId.systemDefault() ;              // Get JVM’s current default time zone.
      

      GregorianCalendar 转换为ZonedDateTime

      可怕的GregorianCalendar 可能是你Calendar 背后的具体类。如果是这样,请从旧类转换为现代类,ZonedDateTime

      GregorianCalendar gc = null ;                    // Legacy class representing a moment in a time zone. Avoid this class as it is terribly designed.
      if( myCal instanceof GregorianCalendar ) {       // See if your `Calendar` is backed by a `GregorianCalendar` class.
          gc = (GregorianCalendar) myCal ;             // Cast from the more general class to the concrete class.
          ZonedDateTime zdt = gc.toZonedDateTime() ;   // Convert from legacy class to modern class.
      }
      

      生成的ZonedDateTime 对象带有一个ZoneId 时区对象。有了该区域,您就可以计算经过的日历天数。

      计算经过的天数

      要以年-月-日的形式计算经过的时间,请使用Period 类。

      Period p = Period.between( zdtStart , zdtStop ) ;
      

      如果您想要总天数作为经过的时间,我们e ChronoUnit

       long days = ChronoUnit.DAYS.between( zdtStart , zdtStop ) ;
      


      关于java.time

      java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

      要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

      Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

      您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

      从哪里获取 java.time 类?

      ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

      【讨论】:

        【解决方案7】:

        这是我使用旧日历对象的解决方案:

        public static int daysApart(Calendar d0,Calendar d1)
        {
            int days=d0.get(Calendar.DAY_OF_YEAR)-d1.get(Calendar.DAY_OF_YEAR);
            Calendar d1p=Calendar.getInstance();
            d1p.setTime(d1.getTime());
            for (;d1p.get(Calendar.YEAR)<d0.get(Calendar.YEAR);d1p.add(Calendar.YEAR,1))
            {
                days+=d1p.getActualMaximum(Calendar.DAY_OF_YEAR);
            }
            return days;
        }
        

        这假设 d0 晚于 d1。如果不能保证,您可以随时测试和交换它们。

        基本原理是取每一年的天数之间的差异。如果他们在同一年,那就是它。

        但他们可能是不同的年份。所以我循环遍历它们之间的所有年份,加上一年中的天数。请注意,getActualMaximum 在闰年返回 366,在非闰年返回 365。这就是为什么我们需要一个循环,你不能只是将年份之间的差异乘以 365,因为那里可能有闰年。 (我的初稿使用了 getMaximum,但这不起作用,因为无论年份如何,它都会返回 366。getMaximum 是任何年份的最大值,而不是这个特定的年份。)

        由于此代码没有假设一天中的小时数,因此它不会被夏令时所迷惑。

        【讨论】:

        • 旧的Calendar 类没有什么好处。 Sun、Oracle 和 JCP 社区都同意这一点,在 310 年前采用 JSR 来取代可怕的遗留日期时间类,例如 DateCalendarSimpleDateFormat。在 2019 年推荐这些遗留类是糟糕的建议。
        • @BasilBourque 该问题使用了 Calendar 类,因此我尝试给出与问题一致的答案。当然,最好使用更新的 LocalDate 等。如果有人问:“在阿巴拉契亚小径徒步旅行时我应该带什么补给品?”,我不会回答:“开车可以更快地到达目的地。” :-)
        【解决方案8】:

        我有上面https://stackoverflow.com/a/31800947/3845798 给出的类似(不完全相同)的方法。

        并且围绕 api 编写了测试用例,对我来说,如果我通过了它就会失败 2017 年 3 月 8 日 - 作为开始日期,2017 年 4 月 8 日作为结束日期。

        很少有日期会相差 1 天。 因此,我对我的 api 做了一些小改动,我当前的 api 现在看起来像这样

           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);
        endDateCalendar.set(Calendar.HOUR_OF_DAY, 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);
        currentDayCalendar.set(Calendar.HOUR_OF_DAY, 0);
        
        
        long remainingDays = (long)Math.ceil((float) (endDateCalendar.getTimeInMillis() - currentDayCalendar.getTimeInMillis()) / (24 * 60 * 60 * 1000));
        
        return remainingDays;}
        

        我没有使用导致我出现一些问题的 TimeUnit.MILLISECONDS.toDays。

        【讨论】:

          【解决方案9】:

          Kotlin 解决方案,完全依赖于日历。最后给出确切的天数差异。 灵感来自@Jk1

           private fun daysBetween(startDate: Calendar, endDate: Calendar): Long {
                  val start = Calendar.getInstance().apply {
                      timeInMillis = 0
                      set(Calendar.DAY_OF_YEAR, startDate.get(Calendar.DAY_OF_YEAR))
                      set(Calendar.YEAR, startDate.get(Calendar.YEAR))
                  }.timeInMillis
                  val end = Calendar.getInstance().apply {
                      timeInMillis = 0
                      set(Calendar.DAY_OF_YEAR, endDate.get(Calendar.DAY_OF_YEAR))
                      set(Calendar.YEAR, endDate.get(Calendar.YEAR))
                  }.timeInMillis
                  val differenceMillis = end - start
                  return TimeUnit.MILLISECONDS.toDays(differenceMillis)
              }
          

          【讨论】:

            【解决方案10】:

            如果您的项目不支持新的 Java 8 类(作为选定答案),您可以添加此方法来计算天数,而不受时区或其他事实的影响。

            它没有其他方法那么快(时间复杂度更高),但它是可靠的,无论如何日期比较很少超过数百年或数千年。

            (科特林)

            /**
                 * Returns the number of DAYS between two dates. Days are counted as calendar days
                 * so that tomorrow (from today date reference) will be 1 ,  the day after 2 and so on
                 * independent on the hour of the day.
                 *
                 * @param date - reference date, normally Today
                 * @param selectedDate - date on the future
                 */
            fun getDaysBetween(date: Date, selectedDate: Date): Int {
            
                        val d = initCalendar(date)
                        val s = initCalendar(selectedDate)
                        val yd = d.get(Calendar.YEAR)
                        val ys = s.get(Calendar.YEAR)
            
                        if (ys == yd) {
                            return s.get(Calendar.DAY_OF_YEAR) - d.get(Calendar.DAY_OF_YEAR)
                        }
            
                        //greater year
                        if (ys > yd) {
                            val endOfYear = Calendar.getInstance()
                            endOfYear.set(yd, Calendar.DECEMBER, 31)
                            var daysToFinish = endOfYear.get(Calendar.DAY_OF_YEAR) - d.get(Calendar.DAY_OF_YEAR)
                            while (endOfYear.get(Calendar.YEAR) < s.get(Calendar.YEAR)-1) {
                                endOfYear.add(Calendar.YEAR, 1)
                                daysToFinish += endOfYear.get(Calendar.DAY_OF_YEAR)
                            }
                            return daysToFinish + s.get(Calendar.DAY_OF_YEAR)
                        }
            
                        //past year
                        else {
                            val endOfYear = Calendar.getInstance()
                            endOfYear.set(ys, Calendar.DECEMBER, 31)
                            var daysToFinish = endOfYear.get(Calendar.DAY_OF_YEAR) - s.get(Calendar.DAY_OF_YEAR)
                            while (endOfYear.get(Calendar.YEAR) < d.get(Calendar.YEAR)-1) {
                                endOfYear.add(Calendar.YEAR, 1)
                                daysToFinish += endOfYear.get(Calendar.DAY_OF_YEAR)
                            }
                            return daysToFinish + d.get(Calendar.DAY_OF_YEAR)
                        }
                    }
            

            单元测试,你可以改进它们我不需要消极的日子,所以我没有测试那么多:

            @Test
                fun `Test days between on today and following days`() {
                    val future = Calendar.getInstance()
                    calendar.set(2019, Calendar.AUGUST, 26)
            
                    future.set(2019, Calendar.AUGUST, 26)
                    Assert.assertEquals(0, manager.getDaysBetween(calendar.time, future.time))
            
                    future.set(2019, Calendar.AUGUST, 27)
                    Assert.assertEquals(1, manager.getDaysBetween(calendar.time, future.time))
            
                    future.set(2019, Calendar.SEPTEMBER, 1)
                    Assert.assertEquals(6, manager.getDaysBetween(calendar.time, future.time))
            
                    future.set(2020, Calendar.AUGUST, 26)
                    Assert.assertEquals(366, manager.getDaysBetween(calendar.time, future.time)) //leap year
            
                    future.set(2022, Calendar.AUGUST, 26)
                    Assert.assertEquals(1096, manager.getDaysBetween(calendar.time, future.time))
            
                    calendar.set(2019, Calendar.DECEMBER, 31)
                    future.set(2020, Calendar.JANUARY, 1)
                    Assert.assertEquals(1, manager.getDaysBetween(calendar.time, future.time))
                }
            
                @Test
                fun `Test days between on previous days`() {
                    val future = Calendar.getInstance()
                    calendar.set(2019, Calendar.AUGUST, 26)
            
                    future.set(2019,Calendar.AUGUST,25)
                    Assert.assertEquals(-1, manager.getDaysBetween(calendar.time, future.time))
                }
            
                @Test
                fun `Test days between hour doesn't matter`() {
                    val future = Calendar.getInstance()
                    calendar.set(2019, Calendar.AUGUST, 26,9,31,15)
            
                    future.set(2019,Calendar.AUGUST,28, 7,0,0)
                    Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
            
                    future.set(2019,Calendar.AUGUST,28, 9,31,15)
                    Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
            
                    future.set(2019,Calendar.AUGUST,28, 23,59,59)
                    Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
                }
            
                @Test
                fun `Test days between with time saving change`() {
                    val future = Calendar.getInstance()
                    calendar.set(2019, Calendar.OCTOBER, 28)
            
                    future.set(2019, Calendar.OCTOBER,29)
                    Assert.assertEquals(1, manager.getDaysBetween(calendar.time, future.time))
            
                    future.set(2019, Calendar.OCTOBER,30)
                    Assert.assertEquals(2, manager.getDaysBetween(calendar.time, future.time))
                }
            

            【讨论】:

              【解决方案11】:
              public int getIntervalDays(Calendar c1,Calendar c2){
                  Calendar first = cleanTimePart(c1);
                  Calendar second = cleanTimePart(c2);
                  Long intervalDays = (first.getTimeInMillis() - second.getTimeInMillis())/(1000*3600*24);
                  return intervalDays.intValue();
              }
              
              private Calendar cleanTimePart(Calendar dateTime){
                  Calendar newDateTime = (Calendar)dateTime.clone();
                  newDateTime.set(Calendar.HOUR_OF_DAY,0);
                  newDateTime.set(Calendar.MINUTE,0);
                  newDateTime.set(Calendar.SECOND,0);
                  newDateTime.set(Calendar.MILLISECOND,0);
                  return newDateTime;
              }
              

              【讨论】:

              • 这些可怕的 Calendar 类早已成为遗留物,多年前已被取代,但 JSR 310 中定义的现代 java.time 类。建议在 2022 年使用它是糟糕的建议。
              【解决方案12】:

              日历day1 = Calendar.getInstance();

              日历 day2 = Calendar.getInstance();

              int diff = day1.get(Calendar.DAY_OF_YEAR) - day2.get(日历.DAY_OF_YEAR);

              【讨论】:

              • 此代码仅在两个实例为同一年的情况下才有效。
              猜你喜欢
              • 1970-01-01
              • 2020-05-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-02-15
              • 1970-01-01
              • 2011-03-05
              相关资源
              最近更新 更多