Skip navigation.
Home

java.sql.Date vs java.sql.Timestamp

Recently I encountered a nasty little runtime bug in java. I call it a bug even though the java.sql.Timestamp javadoc has a Note explaining why.

The bug

If you run this particular JUnit method, it will fail on the last assertion.


public void testTimestampVsDate() {
  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));
}

That's right.. in pseudo code:

  • date equals() stamp
  • date compareTo() stamp is zero (equal)
  • stamp compareTo() date is zero (equal)
  • stamp does not equal date!

If you didn't know already, java.sql.Timestamp extends java.util.Date. So one would think that equality would be symmetric and compareTo() == equals(). One would be wrong! Shocked

Sun's explanation

In the javadoc for java.sql.Timestamp, it states:

Quote:
Note: This type is a composite of a java.util.Date and a separate nanoseconds value. Only integral seconds are stored in the java.util.Date component. The fractional seconds - the nanos - are separate. The Timestamp.equals(Object) method never returns true when passed a value of type java.util.Date because the nanos component of a date is unknown. As a result, the Timestamp.equals(Object) method is not symmetric with respect to the java.util.Date.equals(Object) method. Also, the hashcode method uses the underlying java.util.Date implementation and therefore does not include nanos in its computation.

Due to the differences between the Timestamp class and the java.util.Date class mentioned above, it is recommended that code not view Timestamp values generically as an instance of java.util.Date. The inheritance relationship between Timestamp and java.util.Date really denotes implementation inheritance, and not type inheritance.

The last sentence is where Sun admits that they are really breaking the spirit of inheritance.. implementation inheritance, and not type inheritance.

Why this is painful

This feature can be accidentally stumbled upon (like I just did) pretty easily.

Let's say I hook up some object persistence framework to a database. Hibernate is a good example. In one of my hibernate managed objects there is a java.util.Date instance variable (databaseDate) which maps to a TIMESTAMP field in the database. Pretty common.

Now later on I want to see if that object is relevant for a date that was typed into a web form by an end user (userDate). Again, pretty common. At some point, I'm going to be checking if the dates match. If I use userDate.equals(databaseDate) everything will work as expected. This is because the java.util.Date.equals() method will be used. On the other hand, if I happen to use databaseDate.equals(userDate), nothing will ever evaluate to true.

What is so horrible is that, at compile time, I am working with two java.util.Date objects and all seems right with either comparison. And I am working with two Date objects because java.sql.Timestamp extends java.util.Date! But at runtime, I am left scratching my head.. why would date1 equal date2 but date2 not equal date1?

Now I know why.. Sun decided that implementation inheritance was how this one class out of a million was going to work. I wonder how many other inheritance structures are bastardized in the language?