我有一个程序,我将两个日期相互比较;即使date1
在date2
之前,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
,这导致了不同的数据类型。即使我找到了根本原因并知道如何避免该问题,但我仍然对防止这种缺陷的解决方案非常感兴趣。
答案 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)
使用现代的 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.Timestamp
从java.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。
您使用的是臭名昭著的类。您发现的问题是由于其糟糕的设计使用了错误的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 8及更高版本中。这些类取代了麻烦的旧legacy日期时间类,例如java.util.Date
,Calendar
和SimpleDateFormat
。
目前位于Joda-Time的maintenance 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中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如Interval
,YearWeek
,YearQuarter
和more。