仅比较两个日期的时间部分,忽略日期部分。

36

给定两个日期对象,有什么好的方法可以仅比较它们的时间部分之间的差异,完全忽略年份、月份和日期?

这正好与这个问题相反。

更新:以下是最终代码,供将来参考:

private long differenceBetween(Date currentTime, Date timeToRun)
{
    Calendar currentCal = Calendar.getInstance();
    currentCal.setTime(currentTime);

    Calendar runCal = Calendar.getInstance();
    runCal.setTime(timeToRun);
    runCal.set(Calendar.DAY_OF_MONTH, currentCal.get(Calendar.DAY_OF_MONTH));
    runCal.set(Calendar.MONTH, currentCal.get(Calendar.MONTH));
    runCal.set(Calendar.YEAR, currentCal.get(Calendar.YEAR));

    return currentCal.getTimeInMillis() - runCal.getTimeInMillis();
}

2
是的,它可以。为什么不呢? - kolrie
这个解决方案的讽刺之处在于,你拿了两个包装长整型原始类型的薄包装器对象,调用了一个庞大而复杂的 API 来将 long 转换为 Calendar 对象...只是最终又回到了从 Calendar 对象再次转换为 long 的地方。 - scottb
@scottb - 请查看下面的我的回答,它不会干扰Calendar对象,而只使用Date成员。 - David R Tribble
1
更新 如今,我们可以使用java.time类在一行代码中完成此操作,这些类取代了可怕的DateCalendar。更加简单、更加清晰,并且具有正确的时区处理能力。 - Basil Bourque
8个回答

32

如果想要比较日期的底层二进制(long int)值,可以使用以下方法:

public int compareTimes(Date d1, Date d2)
{
    int     t1;
    int     t2;

    t1 = (int) (d1.getTime() % (24*60*60*1000L));
    t2 = (int) (d2.getTime() % (24*60*60*1000L));
    return (t1 - t2);
}

附录 1

使用这种技术的优点是速度快,因为它直接使用了Date对象的底层long值,而没有进行刻度和日历组件之间的转换(这是相当昂贵和慢的)。它也比搞弄Calendar对象简单得多。

附录 2

上面的代码将时间差作为一个int返回,对于任何一对时间来说都是正确的,因为它完全忽略了日期的年/月/日部分,并且任何两个时间之间的差异都不超过86,400,000毫秒(=1000毫秒/秒×60秒/分钟×60分钟/小时×24小时/天)。


谢谢David,这正是我在寻找的 :) - Beep.exe
非常好!太棒了! - richardaum
这并不总是有效。例如:currentDate: Tue Oct 20 16:28:57 MESZ 2015和validFromTime: Tue Oct 20 01:30:00 MESZ 2015返回错误的结果。 - Michael
@confile 我认为你误解了。这里只是比较从协调世界时(UTC)1970年1月1日星期四的00:00:00开始过去的24小时。如果你用 % (24*60*60*1000L) 计算任何 long 值的模,你只会得到“适合”于24小时的毫秒数。 - Stefan Falk
@DavidRTribble 请问比较2月17:00和8月17:00的预期结果是什么?我认为应该是零。但我得到的结果不是这样的。 - mabi
显示剩余7条评论

21

如果时间是问题,JODA就是答案 :D - bbaja42
@bbaja42 如果你使用GWT的话就不是这样了 :/ - Stefan Falk

6

简而言之

Duration                                  // Span of time, with resolution of nanoseconds.
.between(                                 // Calculate elapsed time.
    LocalTime.now(                        // Get current time-of-day…
        ZoneId.of( "Pacific/Auckland" )   // … as seen in a particular time zone.
    )                                     // Returns a `LocalTime` object.
    ,
    myJavaUtilDate                        // Avoid terrible legacy date-time classes such as `java.util.Date`.
    .toInstant()                          // Convert from `java.util.Date` to `java.time.Instant`, both representing a moment in UTC.
    .atZone(                              // Adjust from UTC to a particular time zone. Same moment, same point on the timeline, different wall-clock time.
        ZoneId.of( "Pacific/Auckland" )   // Specify time zone by proper naming in `Continent/Region` format, never 2-4 letter pseudo-zones such as `PST`, `CEST`, `CST`, `IST`, etc.
    )                                     // Returns a `ZonedDateTime` object.
    .toLocalTime()                        // Extract the time-of-day without the date and without a time zone.
)                                         // Returns a `Duration` object.
.toMillis()                               // Calculate entire span-of-time in milliseconds. Beware of data-loss as `Instant` uses a finer resolution the milliseconds, and may carry microseconds or nanoseconds.

我建议传递类型安全且自解释的Duration对象,而不是仅仅传递毫秒数的整数。

java.time

现代方法使用java.time类,取代了可怕的遗留类,如DateCalendarSimpleDateFormat

Table of all date-time types in Java, both modern and legacy

将您的java.util.Date(UTC中的一个时刻)转换为Instant。使用添加到旧类中的新转换方法。
Instant instant = myJavaUtilDate.toInstant() ;

那表示UTC时间的一个时刻。确定日期和时间需要一个时区。对于任何给定时刻,日期和时间因时区而在全球范围内变化。例如,在法国巴黎的午夜后几分钟是新的一天,而在加拿大魁北克省蒙特利尔仍然是“昨天”。
如果未指定时区,则JVM隐式应用其当前默认时区。该默认值可能会在运行时的任何时刻更改,因此您的结果可能会有所不同。最好将所需/期望的时区明确指定为参数。

请使用格式为大陆/地区正确时区名称,例如America/MontrealAfrica/CasablancaPacific/Auckland。绝不要使用2-4个字母的缩写,如ESTIST,因为它们不是真正的时区,没有标准化,甚至不唯一(!)。

ZoneId z = ZoneId.of( "America/Montreal" ) ;  

如果您想使用JVM的当前默认时区,请请求并将其作为参数传递。如果省略,则隐式应用JVM的当前默认值。最好明确说明,因为默认值可能在运行时由JVM中任何应用程序的任何线程中的任何代码在任何时刻更改。
ZoneId z = ZoneId.systemDefault() ;  // Get JVM’s current default time zone.

ZoneId 分配给 Instant 以生成一个 ZonedDateTime 对象。
ZonedDateTime zdt = instant.atZone( z ) ;

提取时间部分,不包括日期和时区。
LocalTime lt = zdt.toLocalTime() ;

比较。使用 Duration 计算经过的时间。

Duration d = Duration.between( ltStart , ltStop ) ;

请注意,这不是一个公平的比较。一天的长度并非总是24小时,而且并非所有时区的所有时间值都是有效的。例如,在美国进行夏令时换季期间,可能根本没有2点钟。因此,1点到4点可能在某个日期上是3个小时,但在另一个日期上只有2个小时。


关于 java.time

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

Joda-Time项目现在处于维护模式,建议迁移到java.time类。

要了解更多信息,请参见Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310

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

如何获取java.time类?

Table of which java.time library to use with which version of Java or Android

ThreeTen-Extra项目通过增加类来扩展java.time。该项目是java.time可能未来添加的潜在试验场。您可能会在这里找到一些有用的类,例如Interval, YearWeek, YearQuarter以及更多


5

请查看Calendar类。它支持从给定的Date中提取小时,分钟和秒。

Calendar calendar = Calendar.getInstance();
calendar.setTime(yourDateObject);
int hour = calendar.get(Calendar.HOUR);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);

1
Calendar.HOUR_OF_DAY 更合适(24 小时制而非 12 小时制) - JoachimR

4
如果无法使用JODA,创建2个日历对象,将年、月和日设置为0。然后进行比较…

2

我不知道这个函数的实践效果如何,但是我写了一个简单的使用Date对象的函数来满足我的需求。它返回一个布尔值,表示第一个日期值是否大于第二个日期值。输入d1和d2是Date对象。

function CompareTimes(d1, d2) {
             if (d1.getHours() < d2.getHours()) {
                 return false;
             }
             if (d1.getHours() > d2.getHours()) {
                 return true;

             } else {
                 return (d1.getMinutes() > d2.getMinutes());
             }
};

1
如果您只想比较日期的时间部分,那么这将为您提供正确的结果 -
public long compareTimes(Date d1, Date d2)
    {
        long     t1;
        long     t2;
        t1 = d1.getHours()*60*60+d1.getMinutes()*60+d1.getSeconds();
        t2 = d2.getHours()*60*60+d2.getMinutes()*60+d2.getSeconds();
        long diff = t1-t2;
        System.out.println("t1 - "+t1+", t2 - "+t2+",  diff - "+diff);

        return diff;
    }

1
如果无法使用JODA,最短的方法之一可能是将时间转换为字符串,然后进行比较。
DateFormat df = new SimpleDateFormat("HHmmssSZ");
df.format(date1).equals(df.format(date2));

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