Friday, March 18, 2011

As if Time isn't difficult enough! I say this is huge bug in java.sql.Timestamp

BACKGROUND
I found a bug in java.sql.Timestamp that was making my unit tests fail
once it a while when the planets aligned just the right way. I was
storing java.util.Date values to a Db using Hibernate and when they come
back out, they are represented as java.sql.Timestamp, which is a small
wrapper around java.util.Date.

ISSUE
In TimeStamp, they are storing the time in whole seconds and the
remaining nanos seperately. In the Timestamp.compareTo, they call the
super class first (but pass the time part only). So methods like
before() and after() are all in error when milliseconds count. Check out the following code:


/*
* Copyright 2001-2009 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.juddi;

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

import junit.framework.Assert;

import org.junit.Test;


public class TimeStampDateTest {

@Test
public void testDates() {

java.util.Date startDate = new Date(1300286601055l);
java.util.Date modifiedDate = new Date(1300286601334l);
Timestamp timeStamp = new Timestamp(modifiedDate.getTime());

System.out.println(startDate.getTime() + " startDate " + startDate);
System.out.println(modifiedDate.getTime() + " modifiedDate " + modifiedDate);
System.out.println(timeStamp.getTime() + " modifiedDtDB " + timeStamp);

System.out.print("DT:" + startDate.getTime() + " is before " + modifiedDate.getTime() + ": ");
if (startDate.before(modifiedDate)) {
System.out.println("before");
} else {
System.out.println("after ******* WRONG!!!!");
}
Assert.assertTrue(startDate.before(modifiedDate));

System.out.print("DB:" + startDate.getTime() + " is before " + timeStamp.getTime() + ": ");
if (startDate.before(timeStamp)) {
System.out.println("before");
} else {
System.out.println("after ******* WRONG!!!!");
System.out.println("The reason is a bug in Timestamp, it stores the time (whole seconds) and the nanos seperately," +
"and when running a compareTo it only uses the nanos when the Time part is inconclusive, however" +
"it is not inconclusive because the compareTo from the super class (java.util.Date) DOES" +
"already take into account the millies!");
}
Assert.assertTrue(startDate.before(timeStamp));
}

}



The second time the date comparison will fail and it will print out: 'after ******* WRONG!!!!'.

Yes Really!!

Then I was told to read the javadoc:

"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 an object that isn't an instance of java.sql.Timestamp, 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.
"

Ahh that makes it all better! No no no, this is just crap and so easy to fix. Time skrewed up enough, please don't do this Snoracle!

There's a few of my life that I will never get back. The issue is that my unit test failed when the build machine was fast enough to make milli-seconds count when comparing dates!

GRRRR..

--Kurt

3 comments:

Kurt Stam said...

Open Source to the rescue! We're now in the process of fixing it in the IcedTea project. See the progress on bug 676.

Anonymous said...

You should not blame this one on Oracle, it bit me long before Oracle ate the Sun.
But from what I remember it was not documented then.

Kurt Stam said...

Thank you Joda.