为什么Java的Date.after()实际上是较早的日期才返回“ true”?

时间:2018-09-09 15:57:05

标签: java date datetime jdbc language-lawyer

我有一个程序,我将两个日期相互比较;即使date1date2之前,date1.after(date2)仍返回true。时区无效。这两个日期都是世界标准时间。

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

public class Test {
    public static void main(String[] args) throws Exception {
        TimeZone.setDefault(TimeZone.getTimeZone("Etc/UTC"));
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        Date date1 = dateFormat.parse("2018-07-27 01:22:14.077");
        Date date2 = new Timestamp(1532654534390l);
        System.out.println(dateFormat.format(date1));
        System.out.println(dateFormat.format(date2));
        System.out.println(date1.getTime() > date2.getTime());
        System.out.println(date1.after(date2));
    }
}

这将输出:

  

2018-07-27 01:22:14.077
  2018-07-27 01:22:14.390
  错误
  是

这是怎么回事?


在我的真实程序中,Hibernate从日志文件中解析date1,并从数据库中检索date2,这导致了不同的数据类型。即使我找到了根本原因并知道如何避免该问题,但我仍然对防止这种缺陷的解决方案非常感兴趣。

2 个答案:

答案 0 :(得分:8)

此处的基本“问题”是java.sql.Timestamp在扩展java.util.Date的同时并未在指定字段(fastTime,相当于Unix time)中存储毫秒,但在单独的字段nanos中。 after方法仅考虑fastTime字段(这很有意义,因为它可以在所有Date对象上使用)。

在这种情况下,fastTime中的Timestamp四舍五入从1532654534 390 到1532654534 000 < / strong>,它小于另一个日期的1532654534 077 (而“ lower”表示较早的日期)。因此,after()before()在这种情况下不可靠;解决方案是在两个日期上都使用getTime()Timestamp会重载以提供正确的值)并进行比较。

答案 1 :(得分:7)

tl; dr

使用现代的 java.time 类,绝不要可怕的旧式日期时间类。

Instant
.ofEpochMilli( 1_532_654_534_390L ) 
.isAfter(
    LocalDateTime
    .parse( 
        "2018-07-27 01:22:14.077"
        .replace( " " , "T" ) 
    )
    .atOffset( 
        ZoneOffset.UTC
    )
    .toInstant()
)

医生说:请勿将Timestamp对象用作Date

您的代码:

Date date2 = new Timestamp…  // Violates class documentation. 

…违反了课程文档中建立的合同。

  

由于上述Timestamp类和java.util.Date类之间的差异,建议代码不要将java.util.Date的实例一般地查看Timestamp值。 Timestamp和java.util.Date之间的继承关系实际上表示实现继承,而不是类型继承。

该文档指出,尽管java.sql.Timestampjava.util.Date继承,但仍指示您忽略该继承事实。您不要将Timestamp对象用作Date。您的代码完全按照文档告诉您的操作 not 进行操作。

当然,这种假装不是子类的策略是一个荒谬的糟糕的类设计。此黑客是从不使用这些类的许多原因之一。

您看到的有关毫秒小数秒和纳秒不匹配的行为已记录:

  

注意:此类型是java.util.Date和单独的纳秒值的组合。仅整数秒存储在java.util.Date组件中。小数秒-纳米-是分开的。传递不是java.sql.Timestamp实例的对象时,Timestamp.equals(Object)方法从不返回true,因为日期的nanos组件是未知的。结果,Timestamp.equals(Object)方法相对于java.util.Date.equals(Object)方法不对称。另外,hashCode方法使用底层的java.util.Date实现,因此在其计算中不包括nanos。

java.time

您使用的是臭名昭著的类。您发现的问题是由于其糟糕的设计使用了错误的hack。不要去尝试去理解这些课程。只需完全避免使用它们

这些遗留类在几年前被 java.time 类所取代。

解析您的输入字符串。

LocalDateTime ldt = LocalDateTime.parse( "2018-07-27 01:22:14.077".replace( " " , "T" ) ;  // Without a time zone or offset, this value has no specific meaning, is *not* a point on the timeline. 

显然,您知道一个事实,即输入字符串隐式表示UTC时刻。

OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC ) ;  // Assign an offset-from-UTC to give the date and time a meaning as an actual point on the timeline. 

解析其他输入,显然是自1970年1月1日UTC以来的毫秒数。 Instant类比OffsetDateTime更基础,这是UTC中的时刻,根据定义始终是UTC。

Instant instant = Instant.ofEpochMilli( 1_532_654_534_390L ) ;  // Translate a count of milliseconds from 1970-01-01T00:00:00Z into a moment on the timeline in UTC. 

比较。

Boolean stringIsAfterLong = odt.toInstant().isAfter( instant ) ;

关于 java.time

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

目前位于Joda-Timemaintenance mode项目建议迁移到java.time类。

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

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

在哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore