为什么当日期实际上更早时,Java的Date.after()会返回'true'?

Glo*_*del 1 java datetime date jdbc language-lawyer

我有一个程序,我将两个日期相互比较; 即使date1以前date2,也date1.after(date2)回来了true.时区没有效果; 这两个日期都是UTC.

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

这输出:

2018-07-27 01:22:14.077
2018-07-27 01:22:14.390
false
true

这里发生了什么?


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

Glo*_*del 8

这里潜在的"问题"是java.sql.Timestamp,在扩展时java.util.Date,不会在指定字段中存储毫秒(fastTime相当于Unix时间),而是在单独的字段中nanos.该after方法仅考虑该fastTime字段(这是有意义的,因为它可以在所有Date对象上使用).

会发生什么事在这种情况下是,fastTimeTimestamp四舍五入由1532654534 390至1532654534 000,这是比1532654534下077的其他日期(以及更低意味着一个较早的日期).因此,after()before()不是在这种情况下,可靠; 解决方案是在两个日期使用getTime()(重载Timestamp以提供正确的值)并比较它们.

  • 理想情况下,你应该使用`java.time` API (5认同)

Bas*_*que 7

TL;博士

使用现代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()
)
Run Code Online (Sandbox Code Playgroud)

Doc说:不要使用Timestamp对象Date

你的代码:

Date date2 = new Timestamp…  // Violates class documentation. 
Run Code Online (Sandbox Code Playgroud)

......违反了课程文件中规定的合同.

由于时间戳类和上述java.util.Date类之间的差异,建议代码一​​般不要时间戳值作为java.util.Date的实例.Timestamp和java.util.Date之间的继承关系实际上表示实现继承,而不是类型继承.

该文档指出,虽然java.sql.Timestamp从技术上继承java.util.Date,但您被指示忽略继承的事实.你不要将Timestamp对象用作Date.您的代码完全按照文档告诉您不要执行的操作.

当然,这种假装 - 它不是一个子类策略是一个非常糟糕的类设计.这个hack是从不使用这些类众多原因之一.

您看到的行为,关于毫秒分秒和纳秒的不匹配,记录在案:

注意:此类型是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

你正在使用臭名昭着的糟糕课程.你发现的问题是由于他们糟糕的黑客攻击设计糟糕.不要费心去理解这些课程; 完全避开它们.

几年前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. 
Run Code Online (Sandbox Code Playgroud)

显然,您知道输入字符串隐式表示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. 
Run Code Online (Sandbox Code Playgroud)

解析其他输入,显然是自UTC 1970年第一个时刻以来的毫秒数.该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. 
Run Code Online (Sandbox Code Playgroud)

相比.

Boolean stringIsAfterLong = odt.toInstant().isAfter( instant ) ;
Run Code Online (Sandbox Code Playgroud)

关于java.time

java.time框架是建立在Java 8和更高版本.这些类取代麻烦的老传统日期时间类,如java.util.Date,Calendar,和SimpleDateFormat.

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

要了解更多信息,请参阅Oracle教程.并搜索Stack Overflow以获取许多示例和解释.规范是JSR 310.

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

从哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time.该项目是未来可能添加到java.time的试验场.您可以在此比如找到一些有用的类Interval,YearWeek,YearQuarter,和更多.