United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-5103041 : Regression: binary compatibility problem with java.sql.Timestamp.compareTo

Details
Type:
Bug
Submit Date:
2004-09-16
Status:
Closed
Updated Date:
2006-03-24
Project Name:
JDK
Resolved Date:
2006-03-24
Component:
core-libs
OS:
linux,generic,windows_xp
Sub-Component:
java.sql
CPU:
x86,generic
Priority:
P2
Resolution:
Fixed
Affected Versions:
5.0
Fixed Versions:

Related Reports
Backport:
Duplicate:
Duplicate:

Sub Tasks

Description
Just to make this concrete, here's a small program demonstrating the
problem.  Compile and run it, first against 1.4.2, then against
1.5/5.0:

import java.sql.Timestamp;
import java.util.Date;

public class Foo {
  public static void main(String[] args) {
    Date d = new Date();
    Timestamp ts = new Timestamp(d.getTime());
    System.out.println(ts.compareTo(d));
  }
}

Note that this a *binary compatibility problem*!!  The overload
resolution is the same in 1.4.2 and 1.5.0;  in either case, it's
compareTo(Date).  With a 1.4.2 runtime, it dispatches to the version
defined in java.util.Date, with a 1.5.0 runtime it dispatches to the
version defined in java.sql.Timestamp, which promptly casts its
argument to Timestamp (and blows up).  This really feels like a P2 bug
to me.

                                    

Comments
EVALUATION

Fix integrated in jdk 5.0u7 as of Jan 3. This should be available in the builds of jdk5.0u7 after Jan 3.
                                     
2006-01-17
EVALUATION

This is getting fixed in Mustang and jdk5 update release as well. 
I have got the approval and this fix should be in soon.
All those requesting for this fix watch out for this fix and comeback with your comments.
                                     
2005-10-13
SUGGESTED FIX

public int compareTo(java.util.Date d) {
    if (d instanceof Timestamp)
        return compareTo((Timestamp)d);
    else
        return  - (d.compareTo(this));
}

###@###.### 2005-06-07 19:02:25 GMT
                                     
2005-06-07
EVALUATION

A very interesting bug...  here's what I think happened.

In the fix for 4227983 (java.sql.Timestamp didn't overload compareTo, setTime
and getTime, delta 1.35, 2000/02/02, integrated into 1.4.0), two compareTo
methods were added to the Timestamp class, one taking a Timestamp and the other
taking an Object.  The compareTo(Object) method was specified as follows:

     * Compares this Timestamp to another Object. If the Object is 
     * a Date, this function behaves like compareTo(Timestamp). 
     * Otherwise, it throws aClassCastException (as Timestamps are 
     * comparable only to other Timestamps)
     * ...

The intent of this change was to allow Timestamps to be compared to Dates.

A later fix, for 4631234 (java.sql.Timestamp.compareTo no longer accepts
java.util.Dates in 1.4, delta 1.46 2002/04/01, integrated into 1.4.1), revised
the specification of the compareTo(Object) method instead to say:

     * Compares this <code>Timestamp</code> object to the given 
     * <code>Object</code>, which must be a <code>Timestamp</code>
     * object. If the argument is not a <code>Timestamp</code> object,
     * this method throws a <code>ClassCastException</code> object.
     * (<code>Timestamp</code> objects are 
     * comparable only to other <code>Timestamp</code> objects.)

In other words, the intent of this change was to require a ClassCastException
to be thrown when comparing a Timestamp to a Date.  Such behavior would cause
the submitter's code to fail.

The change for 4631234 did not modify any code.  It appears to have been made
under the assumption that the existing code already behaved according to the
new specification.  Due to the presence of a compareTo(Date) method in the Date
superclass, however, such comparisons were actually allowed, and so the
submitter's code actually worked... until recently.

The generification changes for Tiger (4964490) added the compareTo(Date) method
to Timestamp in order to preserve what was assumed (from the specification) to
be the current behavior; unfortunately the fact that the current behavior was
different from the specified behavior was not noticed at that time.  That new
method is what is now causing the submitter's code to receive the (specified)
ClassCastException.

A reasonable fix for this bug would be to revise the specification of the
Timestamp.compareTo(Date) method to allow Timestamp to be comparable to Dates
once again, and to redefine that method as

    public int compareTo(java.util.Date d) {
        return d.compareTo(this);
    }

(Of course the real problem here is that java.sql.Timestamp should not have
been made a subclass of java.util.Date in the first place, as explained in item
7 of Bloch's "Effective Java", but that's water under the bridge.)

-- ###@###.### 2004/9/16

I agree with the above comprehensive assesment, and I'll work with the JCK team
and 1.5.x team to get fix for this. A specific determination as to the most
ideal delivery vehicle still has to be determined, but likely Mustang at this
point.

###@###.### 2004-09-16

I am looking into fixing this bug.

The following lines will be getting removed from javadocs for the method
  public int compareTo(java.util.Date o) 

      * @exception ClassCastException if the argument is not a
      *        <code>Timestamp</code> object
      * @since    1.5
      */
     // This forwarding method ensures that the compareTo(Date) method defined
     // in java.util.Date is not invoked on a Timestamp

###@###.### 2005-05-02 05:25:12 GMT

1. I will be removing the above lines from mustang i.e. jdk 6.0 and will not be doing the changes in jdk5.0uX versions.
I hope that is fine.

###@###.### 2005-06-07 13:58:51 GMT

2. Also will change the method code to, in addition to above :-
public int compareTo(java.util.Date d) {
       return  - (d.compareTo(this));
   }

###@###.### 2005-06-07 14:39:59 GMT

Since this is a regression and a binary incompatibility,
it is not unlikely that we will get requests for backports.
So I think a backport to a Tiger update must be considered.

The proposed solution is problematic:

java.util.Date d1 = new java.sql.Timestamp(...);
java.util.Date d2 = new java.sql.Timestamp(...);
d1.compareTo(d2); // stack overflow

See "Suggested Fix" section for an alternative solution.

###@###.### 2005-06-07 19:02:25 GMT

I propose the following fix, other fixes propose either don't work with 1.4.x and 1.5.x or if they work don't produce the same results.

Index: src/java/sql/Timestamp.java

(cvs diff -wu Timestamp.java)
===================================================================
RCS file: /m/jws/jdbc4.0/src/java/sql/Timestamp.java,v
retrieving revision 1.5
diff -w -u -r1.5 Timestamp.java
--- src/java/sql/Timestamp.java    18 Jul 2005 19:33:37 -0000    1.5
+++ src/java/sql/Timestamp.java    21 Jul 2005 11:13:21 -0000
@@ -462,14 +462,22 @@
     *        <code>Timestamp</code> object
     * @since    1.5
     */
-    // This forwarding method ensures that the compareTo(Date) method defined
-    // in java.util.Date is not invoked on a Timestamp
    public int compareTo(java.util.Date o) {
-        return compareTo((Timestamp)o);
-    }
+        if(o instanceof Timestamp) {
+            // When Timestamp instance compare it with a Timestamp
+            // Hence it is basically calling this.compareTo((Timestamp))o);
+            // Note typecasting is safe because o is instance of Timestamp
+            return compareTo((Timestamp)o);
+        } else {
+            // When Date doing a o.compareTo(this)
+            // will give wrong results.
+            Timestamp ts = new Timestamp(o.getTime());
+            return this.compareTo(ts);
+        }
+    }

    static final long serialVersionUID = 2745179027874758501L; 

The test cases against which I verified the fix are already as given here and as below.

/////////////// Test Case 1 ///////////////////////
import java.sql.Timestamp;
import java.util.Date;

public class Foo {
 public static void main(String[] args) {
   Date d = new Date();
     Timestamp ts = new Timestamp(d.getTime());
   System.out.println(ts.compareTo(d));
   }
}

/////////////// Test Case 2 ///////////////////////
import java.sql.Timestamp;
import java.util.Date;

public class Foo1 {
 public static void main(String[] args) {

   Date d1 = new Date();
     java.util.Date d1 = new java.sql.Timestamp(d.getTime());
     java.util.Date d2 = new java.sql.Timestamp(d.getTime());
     System.out.println("Value is : "+d1.compareTo(d2));
 }
} 

Except the comments(//This forwarding) which I am not sure can be backported, I think this fix of the code can be backported without problems, if required.
Please comment for the fix.
                                     
2004-09-22



Hardware and Software, Engineered to Work Together