【问题标题】:Java Calendar: Getting Difference Between Two Dates/Times - Off by OneJava 日历:获取两个日期/时间之间的差异 - 减一
【发布时间】:2012-10-23 07:28:34
【问题描述】:

我看到了很多关于这个主题的问题和答案,但没有一个能解决我的特定问题。我扩展了 java Calendar 类(标准——没有第三方库),并且需要找到两个任意日期之间的 days 差异。

方法:

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

有时是,有时不是。即使是同一天的测试也可以减少一个。怎么回事?

【问题讨论】:

  • 附带说明,您确定扩展 Calendar 是一个好主意,而不是生成一个在Calendar 实例上运行的帮助程序库吗?跨度>
  • 一个问题是你假设一天有 86400000 毫秒。

标签: java date calendar datediff


【解决方案1】:

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 这样的小而好的库。完美运行,完全满足您的需求。
  • 仅供参考,Joda-Time 项目现在位于 maintenance mode,团队建议迁移到 java.time 类。
  • @BasilBourque 谢谢,使用来自 JRE 8 库的java.time 添加了版本
  • LocalDateTime 是错误的类。该类故意丢失所有偏移量和时区信息。因此,您将使用一般的 24 小时工作日,并忽略时区中表示的异常,例如夏令时 (DST)。详情请见my Answer
  • 对我不起作用。它说 .toInstant() 不存在!它必须是什么日历?
【解决方案2】:

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

这是我目前的做法,

    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);
【解决方案3】:

tl;博士

ChronoUnit.DAYS.between( then , now )

简单计算

您在代码中假设每天正好是二十四小时。不对。 Daylight Saving Time (DST) 等异常表示天数可能会有所不同,例如 23 小时、25 小时或其他数字。时区的真正含义是跟踪这些异常的历史。

此外,您假设这一天从午夜开始。不对。由于异常,有些日子从其他时间开始,例如01:00

避免使用旧的日期时间类

您正在使用麻烦的旧日期时间类,例如 java.util.Datejava.util.Calendarjava.text.SimpleTextFormat,现在是 legacy,被 java.time 类所取代。

时区

continent/region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 3-4 个字母的缩写,例如 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 );

this code run live at IdeOne.com

then.toString(): 2017-01-23T12:34:56.123456789-05:00[美国/蒙特利尔]

now.toString(): 2017-03-01T21:26:04.884-05:00[美国/蒙特利尔]

天数:37

将旧实例转换为 java.time

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

ZonedDateTime zdt = myGregCal.toZonedDateTime() ;

如果您知道 Calendar 实例实际上是 GregorianCalendar,请强制转换它。

GregorianCalendar myGregCal = (GregorianCalendar) myCal ;

半开

java.time 类通过半开方法定义时间跨度,其中开始是包含,而结束是独占


关于java.time

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

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

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

从哪里获得 java.time 类?

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

【讨论】:

    【解决方案4】:

    我的职责是应您的要求。我使用的是日历,在比较之前我将所有时间部分都设置为 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;
        }
    

    【讨论】:

      【解决方案5】:

      根据其他用户的建议,您还需要将日历的毫秒值设置为零以仅比较日期。这可以通过以下代码sn-p来实现:

      someCalendar.set(Calendar.MILLISECOND, 0)
      

      另外,请注意时区变化(例如,从冬季时间转到夏季时间)可能意味着一天的时间多于或少于 86,400,000 毫秒。

      【讨论】:

      • 优雅的解决方案!我应该更彻底地阅读文档(就像我们都有时间一样!)。
      • 仅供参考,麻烦的旧日期时间类,如 java.util.Calendar 现在是 legacy,被 java.time 类取代。
      【解决方案6】:

      您在第一步中通过将时间设置为午夜发现了问题:确保所有小时、分钟和秒都为零。但你走得还不够远!您还必须确保 毫秒 也被清零。

      这里有一些代码可以做到这一点。

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

      【讨论】:

      • 不需要 truncate 方法,他可以将 Calendar 实例与 set(Calendar.MILLISECOND, 0) 的毫秒数归零。
      • @DuncanJones 更好!没有看到日历库的那个方面,谢谢!!
      • @DuncanJones:请将此添加为答案,以便我给予您适当的信任。
      【解决方案7】:

      我认为您需要从日期中截断时间部分,然后再计算如下差异:

         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);
      

      希望这行得通。

      【讨论】:

      • @ScottBiggs:很高兴知道。请不要忘记接受答案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-22
      • 2017-03-27
      • 1970-01-01
      • 1970-01-01
      • 2013-09-08
      相关资源
      最近更新 更多