在Java中比较Date对象和TimeStamp的区别

32

当我测试这段代码时:

java.util.Date date = new java.util.Date();
java.util.Date stamp = new java.sql.Timestamp(date.getTime());

assertTrue(date.equals(stamp));
assertTrue(date.compareTo(stamp) == 0);
assertTrue(stamp.compareTo(date) == 0);
assertTrue(stamp.equals(date));
我期望得到一个 true、true、true、false。原因如下:
在 java.sql.Timestamp 的 javadoc 中,注明了以下内容:
注意:这种类型是一个 java.util.Date 和一个单独的纳秒值的组合。只有整数秒存储在 java.util.Date 组件中。小数秒 - 纳秒 - 是分开的。当传递 java.util.Date 类型的值时,Timestamp.equals(Object) 方法永远不会返回 true,因为日期的纳秒组件是未知的。因此,Timestamp.equals(Object) 方法在与 java.util.Date.equals(Object) 方法相比中不对称。此外,hashcode 方法使用基础的 java.util.Date 实现,因此在计算中不包括纳秒。
由于以上提到的 Timestamp 类和 java.util.Date 类之间的差异,建议代码不将 Timestamp 值通用地视为 java.util.Date 的实例。Timestamp 与 java.util.Date 之间的继承关系实际上表示实现继承,而不是类型继承。
但实际上我会得到 true、false、true、false。你有什么想法吗?
编辑:我使用 equals 方法检查两个日期时出现了问题,但其中一个 Date 对象来自 Hibernate 类,并且调试时我发现对象包含一个 TimeStamp。因此 equals 方法的结果为 false,然后我找到了这个链接:http://mattfleming.com/node/141 但是,当我尝试运行代码时得到的结果不同...如果不能使用 equals 和 compareTo 方法,那么我应该使用什么方法来检查 2 个日期是否相同?!?

基本上:如果你想比较两个同类对象(无论是日期还是时间戳),那么没有问题;不要比较两个异类对象。我们可以尝试从源头解决问题,也就是说:为什么你需要比较它们? - Viruzzo
我正在比较两个日期对象,但其中一个隐式对象是一个时间戳,它是一个日期类型。如果继承在其他对象中起作用,为什么在这里不起作用呢? - Torres
1
由于Timestamp的实现很愚蠢,你应该将这两个类视为没有关联,就像编写它的人的意图一样。 - Viruzzo
10个回答

10

tl;dr

使用现代的java.time类,而不是那些麻烦的旧日期时间类。

myPreparedStatement.setObject(
    … , 
    Instant.now()                // Capture the current moment in UTC.
)

旧的日期时间类设计不佳

更直白地说,java.sql.Timestamp/.Date/.Time类是一种糟糕的hack。和java.util.Date/.Calendar一样,它们都是由于设计选择不当而产生的。

应尽可能简短地使用java.sql类型,仅用于数据库中数据的输入/输出传输。不要用于业务逻辑和进一步处理。

java.time

旧的日期时间类已被Java 8及更高版本内置的java.time框架所取代。这些新类由JSR 310定义,受到Joda-Time库的启发,并由ThreeTen-Extra项目进行了扩展。
最终,我们应该看到JDBC驱动程序更新以直接使用这些java.time类型。但在那一天之前,我们需要转换为/from java.sql类型。对于这种转换,请调用旧类中新增的新方法Instant是UTC时间轴上的一个瞬间,分辨率为纳秒
Instant instant = myJavaSqlTimestamp.toInstant();

要走另一个方向:

java.sql.Timestamp ts = java.sql.Timestamp.valueOf( instant );

应用时区以获取挂钟时间
ZoneId zoneId = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = ZonedDateTime.ofInstant( instant , zoneId );

java.time类具有清晰而明智选择的类设计。因此,您可以按预期使用equals和compareTo方法。请注意,带有UTC偏移量或时区的类还提供isEqual、isBefore和isAfter方法。这些方法通过考虑时间线上的时刻及其时间顺序进行比较。equals和compareTo方法也考虑偏移量或时区。
最小化使用java.sql,同时最大限度地利用java.time可使问题得到解决。
在Hibernate中,使用转换器处理java.time。
JDBC 4.2
自JDBC 4.2及更高版本开始,您无需完全使用旧版类。您可以通过getObject和setObject方法直接与数据库交换java.time对象。
myPreparedStatement.setObject( … , instant ) ;

和检索。

Instant instant = myResultSet.getObject( … , Instant.class ) ;

请注意,许多数据库无法以java.time中使用的纳秒分辨率存储时间点。您可能希望明确截断而不是让您的JDBC驱动程序隐式地这样做。
Instant instant = Instant.now().truncatedTo( ChronoUnit.MILLIS ) ; // Lop off any nanoseconds & microseconds, keeping only the milliseconds, to match limitations of database. 

关于 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类?

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


8

Nican解释了equals部分,关于compareTo

  • Timestamp有一个compareTo(Date)方法,它在内部将其转换为Timestamp
  • Date通过向下转型进行比较(因为Timestamp是它的子类);但正如javadoc所述:“Timestamp和java.util.Date之间的继承关系实际上表示实现继承,而不是类型继承”

在我看来,这当然是个可怕的想法。


5

我在测试中遇到了同样的问题,我想要比较java.util.Datejava.sql.Timestamp对象。

我将它们转换为LocalDate,这样就可以比较了:

import org.apache.commons.lang.ObjectUtils;

// date1 and date2 can be java.util.Date or java.sql.Timestamp
boolean datesAreEqual = ObjectUtils.equals(toLocalDate(date1), toLocalDate(date2));

其中toLocalDate是:

import org.joda.time.LocalDate;
import java.util.Date;

public static void LocalDate toLocalDate(Date date)
{
    return date != null ? LocalDate.fromDateFields(date) : null;
}

4
  1. 问题:date.equals(stamp)返回true,而stamp.equals(date)返回false。原因:Date忽略时间戳的纳秒部分,由于其他部分恰好相等,所以结果是相等的。小数秒 - 纳秒 - 是独立的。当传递java.util.Date类型的值时,Timestamp.equals(Object)方法永远不会返回true,因为日期的nanos组件是未知的。详见此处

    1. 问题:date.compareTo(stamp) == 0返回false,而stamp.compareTo(date) == 0返回true。原因:根据这个bug,compareTo函数的行为就是这样的。

3
尝试使用“字符串”重新创建日期对象,例如:
Date date = new Date();
Date stamp = Timestamp(date.getTime());

SimpleDateFormat ft = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String date1String = ft.format(date);
String date2String = ft.format(stamp);

Date date1 = ft.parse(date1String);
Date date2 = ft.parse(date2String);


assertTrue(date1.equals(date2)); // true
assertTrue(date1.compareTo(date2) == 0); //true
assertTrue(date2.compareTo(date1) == 0); //true
assertTrue(date2.equals(date1)); // true

2
Timestamp的纳秒值不是指纳秒数,而是毫秒的纳秒分辨率数字(即小数秒)。因此,在Timestamp构造函数中,它将超级时间设置为没有毫秒。因此,Timestamp的成员fastTime(在Date的compareTo()中使用)始终具有比相应的Date更低的值(当然,如果它没有小数秒)。请查看Timestamp源代码的第110行

1

我已经将日期和时间戳转换为日历对象,然后比较了单个日历对象的属性:

Calendar date = Calendar.getInstance();
date.setTimeInMillis(dateObject.getTime());
Calendar timestamp = Calendar.getInstance();
timestamp.setTimeInMillis(timestampObject.getTime());
if (effettoAppendice.get(Calendar.DAY_OF_MONTH) == timestamp.get(Calendar.DAY_OF_MONTH) &&
    effettoAppendice.get(Calendar.MONTH) == timestamp.get(Calendar.MONTH) &&
    effettoAppendice.get(Calendar.YEAR) == timestamp.get(Calendar.YEAR)) {
    System.out.println("Date and Timestamp are the same");
} else {
    System.out.println("Date and Timestamp are NOT the same");
}

希望这可以帮助到你。

0
关于实现继承和类型继承的小笔记... "一个对象的类定义了对象的实现方式。相比之下,一个对象的类型只是指其接口。类继承(实现继承)用另一个对象的实现来定义对象的实现。类型继承描述了何时可以使用一个对象代替另一个对象。" 时间戳和日期类具有实现继承,正如JAVADOC所说。

0

遗憾的是,Timestamp类重载了equals(Object)方法,使用equals(Timestamp)进行Timestamps比较很困难。

equals(Object) javadocs中说:

测试此Timestamp对象是否等于给定对象。添加了此版本的equals方法以修复Timestamp.equals(Timestamp)的不正确签名,并保留与现有类文件的向后兼容性。注意:此方法与基类中的equals(Object)方法不对称。

我的经验法则是永远不要将时间戳进行相等比较(这几乎没有用),但如果您确实需要检查相等性,请使用getTime()的结果(从1970年1月1日00:00开始的毫秒数)进行比较。


1
为什么说“可悲”?类应该提供有意义的equals()实现,而不是使用默认的忽略实际数据值的实现。 - Viruzzo
3
通过这样做,违反了相等的契约:一个日期等于一个时间戳,但时间戳不等于日期。它们之间不应该具有继承关系,并且它们应该是不可变的。 - JB Nizet
是的,但是String、Integer等类型在不违反equals协定的情况下进行重写。它们还覆盖了“Object”的equals实现。Timestamp重写了“Date”的equals实现。 - JB Nizet
@Viruzzo:覆盖equals()没有任何问题,但是重载会有前面描述的问题。Comparable<T>接口对其元素的数学约束更加严格,因此标准排序集合可以正常工作(例如以确定的顺序表示元素并与equals()兼容,但equals()甚至不对称)。 - Gabriel Belingueres
1
我同意 Timestamp.equals(Date) 是个不好的想法(至少在它的实现方式上),但是这个答案提到的是 Timestamp.equals(Timestamp) - Viruzzo
显示剩余2条评论

0

他关心的是compareTo()这个错误的断言,而不是equals() - sarumont

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