在 Java 中将 Date 对象与时间戳进行比较tl;博士旧的日期时间类设计不佳java.time关于 java.time

2022-09-01 10:59:49

当我测试此代码时:

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

我会期待一个真,真,真,假。因此:

在 javadoc for java.sql.Timestamp 中,它指出:

注意:此类型是 java.util.Date 和单独的纳秒值的组合。只有整数秒存储在 java.util.Date 组件中。秒的小数部分 - 纳米 - 是分开的。Timestamp.equals(Object) 方法在传递 java.util.Date 类型的值时永远不会返回 true,因为日期的 nanos 分量是未知的。因此,Timestamp.equals(Object) 方法相对于 java.util.Date.equals(Object) 方法不是对称的。此外,哈希码方法使用底层java.util.Date实现,因此在其计算中不包括nanos。

由于上面提到的 Timestamp 类和 java.util.Date 类之间存在差异,因此建议代码不要将时间戳值通常视为 java.util.Date 的实例。Timestamp 和 java.util.Date 之间的继承关系实际上表示实现继承,而不是类型继承。

但相反,我会得到一个真,假,真,假。有什么想法吗?

编辑:当我使用 equals 方法检查两个 Dates 时,会出现此问题,但其中一个 Date 对象来自 Hibernate 类和调试,我看到该对象包含一个 TimeStamp。所以等式方法的计算结果为false,然后我发现这个:http://mattfleming.com/node/141

但是当我尝试代码时,我得到不同的结果...如果我不能使用两个等于和compareTo,我应该用什么来检查2个日期是否相同?!?!


答案 1

tl;博士

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

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

旧的日期时间类设计不佳

更直白地说,java.sql.Timestamp/。日期/。时间课是一个黑客,一个糟糕的黑客。像java.util.Date/。日历,它们是设计选择不当的结果。

java.sql类型应尽可能简短地使用,仅用于在数据库内/向外传输数据。不要用于业务逻辑和进一步的工作。

java.time

旧的日期时间类已被 Java 8 及更高版本中内置的 java.time 框架所取代。这些新类由 JSR 310 定义,灵感来自非常成功的 Joda-Time 库,并由 ThreeTen-Extra 项目进行扩展。

最终,我们应该看到JDBC驱动程序更新为直接使用这些java.time类型。但在那一天之前,我们需要转换为/从java.sql类型。对于此类转换,请调用添加到旧类的新方法

即时是时间轴上的一个时刻,以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类有一个干净明智的选择类设计。因此,您可以按预期使用 和。请注意,具有与 UTC 或时区偏移的类还提供 、 和 方法。这些方法通过考虑时间轴上的时刻,它们的时间顺序来进行比较。和 方法还会考虑偏移量或时区。equalscompareToisEqualisBeforeisAfterequalscompareTo

尽量减少java.sql的使用,同时最大限度地使用java.time会使问题变得毫无意义。

在Hibernate中,使用java.time的转换器。

JDBC 4.2

从 JDBC 4.2 及更高版本开始,您根本不需要使用旧版类。您可以通过 &t 方法直接将 java.time 对象与数据库交换。getObjectsetObject

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的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuarter


答案 2

Nican解释了这部分,关于:equalscompareTo

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

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