Java Date Timestamp Date Comparison Pitfalls
Scenario reenactment
-
In the mysql database (innodb engine) tab table has a createAt field of type datetime(6) precise to milliseconds.Of course we know that mysql’s date field is only accurate to the second level by default, if you want to millisecond subtlety can be defined as datetime(6), from version 5.6.4 onwards support
-
business interface A through the ORM framework to deposit a record in the table, here createAt deposit2016-04-13 15:20:39.152
-
business interface B needs to check this record loaded into the entity class Entity, where the createAt property is of type Date, the value of which is the date above. (Note: added
@Temporal(TemporalType.TIMESTAMP)
annotation, then the field type is Timestamp. this scenario is Date) -
Get the current system time, note that Timestamp is used here Date now = new Timestamp(System.currentTimeMillis()) The millisecond formatted string is 2016-04-13 15:20:39.952 You’ll notice the extra milliseconds decimal after 952
-
call now.after(entire.createAt) expecting to return true, but actually returning false!!!
This results in a strange situation: when a time-stamped record is first deposited in the database, it is taken out and compared with the current system timestamp, and it turns out that the time previously deposited is in the future! What is happening here to cause current system time .after( previously stored time ) = false?
Source code analysis
The problem can be seen by looking at the source code. First of all, the first if judgment of the Date class method getMillisOf() is not empty, but isNormalized() returns true. this normalize is a private property in CalendarDate, which will become false as soon as set/add or other methods to modify the time are called. for other explanations, please See the comments of cdate. Second, the Date constructor directly assigns the system’s millisecond-level long value to fasttime. Then, the Timestamp constructor intercepts the seconds into fasttime, and the milliseconds are subtly counted into nanos. place.
/*
* If cdate is null, then fastTime indicates the time in millis.
* If cdate.isNormalized() is true, then fastTime and cdate are in
* Otherwise, fastTime is ignored, and cdate indicates the
* time.
*/
private transient BaseCalendar.Date cdate;
public Date() {
this(System.currentTimeMillis());
}
public Date(long date) {
fastTime = date;
}
static final long getMillisOf(Date date) {
if (date.cdate == null || date.cdate.isNormalized()) {
return date.fastTime;
}
BaseCalendar.Date d = (BaseCalendar.Date) date.cdate.clone();
return gcal.getTime(d);
}
Timestamp constructor.
public Timestamp(long time) {
super((time/1000)*1000); // milliseconds divided by 1000 rounded to just seconds, then multiply by 1000 as the millisecond value
nanos = (int)((time%1000) * 1000000);
if (nanos < 0) {
nanos = 1000000000 + nanos;
super.setTime(((time/1000)-1)*1000);
}
}
Debugging breakdown
Then we compare new Date() vs new Timestamp(System.currentTimeMillis()) The difference between the two storage methods can be clearly distinguished by the following two diagrams, just pay attention to fastTime.
- The last three digits of the Date fastTime are 956, which means it is accurate to milliseconds.
- The last three digits of timestamp are 000, which means it is intercepted to the second, while the real millisecond 166 is put into nanos
Conclusion notes
- The root cause of the error is a mix of Date and Timestamp, which causes the date comparison to fail.
The time type that ORM retrieved from the database is Date
first
(see the picture at the end of the article), and the current timestamp is wrongly used as Timestampsecond
(see the picture at the end of the article), just change it to new Date(). - if you can’t avoid mixing, then don’t use after() before() to do date comparison! Directly use getTime() to compare the size of long can be! If you are interested in this can look at the Timestamp getTime() source code, it will put the nanos back together in the value!
How to reproduce
Two long type data, one 800 milliseconds, one 900 milliseconds, you can see that after (before similar, compareTo caution) returned the wrong result.