Saturday afternoon. Perfect timing for an exhaustive debug session.
So here we are, debugging a relatively straight-forward algorithm that handles objects based on their Date field. Supposedly, a piece of cake.
A few hours later (and 3 mins prior to solving the issue), we notice that a collection of Date objects sometimes contains objects of type TimeStamp.
A quick glimpse on the TimeStamp class javadoc reveals the following:
TimeStamp is a composite of a java.util.Date and a separate nanoseconds value
and
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.
To put the same statements into something more comprehensible:
Date d = new Date(); Date ts = new Timestamp(d.getTime()); System.out.println(ts.equals(d)); //returns false System.out.println(d.equals(ts)); //returns true System.out.println(ts.compareTo(d)); //return 0 System.out.println(d.compareTo(ts)); //returns 1
Now, it’s not very polite to have two references of type Date, with ‘o1.equals(o2) = true’ and ‘o2.equals(o1) = false’ :) If you have an application that fetches data from the underlying database via ORM (in this scenario we use Hibernate) and from the GUI, chances are you will have this situation.
But the biggest problem has yet to come!
By now, we all learned that TimeStamp extends Date and adds the nanosecond ’support’. OK, let’s accept that. So when implementing the compareTo method, they obviously took care to check if it the argument they’re comparing to was a TimeStamp or a Date. If it was a Date, they created a temporary TimeStamp object from its milliseconds-from-1-1-1970 value (getTime() method), and the output was correct (in the example above, it returns 0). But what about the last example? ‘d.compareTo(ts) = 1′ ?! How can that be? If the Date object has no perception of nanos and the TimeStamp object is created from its getTime() method, how can the Date be *after* the TimeStamp?
By bad design decisions, i dare say :)
In the last paragraph, when i told you about the TimeStamp’s compareTo(Date d) method, I left something out intentionally. Now i can drive the point home :) Here’s the original method implementation (taken from the java.sql.TimeStamp source code):
public int compareTo(java.util.Date 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);
}
}
Notice the comments in the ‘else’ block? Nice!
After some more digging through the TimeStamp and Date implementation, we found the epicenter of this stupidity :) Take a look at the TimeStamp constructor:
public Timestamp(long time) {
super((time/1000)*1000);
nanos = (int)((time%1000) * 1000000);
if (nanos < 0) {
nanos = 1000000000 + nanos;
super.setTime(((time/1000)-1)*1000);
}
}
See the first line? It removes the millisecond part and calls the Date constructor. Everything smaller than a second is kept in the ‘nanos’ field. Very clever. Although it’s not a photon, TimeStamp is of dual nature :)
When calling the compareTo method on a Date object it compares the ‘fastTime’ fields. Unfortunately, TimeStamp changed the original ‘fastTime’ field by subtracting everything that’s smaller than a second and putting that data in the separate field called ‘nanos’. Meanwhile, calling the equals() method will compare the same object by calling their getTime() method which will produce the correct result since the TimeStamp implementation takes care of adding back the ‘nanos’ part to the ‘fastTime’ field.
Sun Microsystems, you’re doing it wrong.
Conclusion: when using TimeStamp and Date objects interchangeably, the preferred way to go is force casting to one of them…
p.s. credits for this post go to !alk and fressner, we all invested a few hours in this post :)
[Update 16/10] Seems we were the only ones not using the Joda Time library
By golly, al vi serete kvake.
I thought it was a good article until you started injecting apostrophes at random points. For GOD’s sake, it’s not that hard!
Plural - NO apostrophe - “there are ten elephants”
Possessive - apostrophe - “calyx’s grasp of grammar left a lot to be desired”
Possessive plural - apostrophe - “The elephants’ home was quickly becoming too small…”
chang: Thanks for the tip, english is obviously not our primary language, so any free english lesson is good. Thanks :-)
Thanks for this very much post-related comment!
thanks chang! i corrected the errors i could find…
chang: Am I to assume your font doesn’t have backticks? The things he was emphasizing had a backtick preceding them and a regular quote following them.
`for example’
Joda Time FTW
Joda Time is like Tivo. Sure, you can live without it, but once you get it you’re never going back.
As polymorphism go, the equals and compareTo functions on objects are from the start a weird concept. A square is a quadrilateral but a quadrilateral is not a square… The basics.
I must admit that the constructor move is not acceptable though. A square HAS to be its equivalent quadrilateral.
Java should have more descriptive comparison operators/functions…
Joda seems cool.
This is nothing new. I filed a bug on this matter to SUN bug database more than a year ago. You can see it at
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6587778.
(And yes, I still have the email from SUN proving my submission)
Couple of points;
1) “Conclusion: when using TimeStamp and Date objects interchangeably, the preferred way to go is force casting to one of them…” Casting won’t change which equals method gets called. A Timestamp is still an instance of Timestamp no matter what the Reference variable is type cast as.
2) Read Effective Java by Joshua Block. It covers this issue in chapter 3 along with an explanation of why implementing equals on any polymorphic object graph is difficult / impossible. To quote the book “This behaviour of the Timestamp class was a mistake and should not be emulated.”
@Sharebear:
1) By forcing, I meant something like:
Date d = new Date (ts.getTime());
or vice versa.
Although I admit the words I used do not communicate it very well :)
2) Thanks for the tip!
Yeah, I work with Hibernate and I’ve done Date-based reports where things like this were an issue.
Discovering this on your own, you almost want to murder some of Java’s designers. It’s that much of an “epic fail”.
Anyway, you did a good write up of the situation. I felt your pain.
That eqaual scenario in which d.equals(ts) is true but ts.equals(d) is not is even against the Javadoc for Object.equals:
http://java.sun.com/javase/6/docs/api/java/lang/Object.html#equals(java.lang.Object)
Almost always inside equals methods it’s better to use getClass() instead of instanceof to restrict the cases in which two objects can be equal to the objects of the same class:
equals(Object o) {
if (null != o && o.getClass().equals(getClass()) {
…
}
…
}
@Behrang: the javadoc for Object.equals states:
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
But anyway, the problem is in the broken inheritance. TimeStamp changed the behavior of the field he inherited from Date - that causes all the weird stuff :)
Hey, Chang. I would have given your response more credit had you simply used God’s instead of GOD’s. Next you’ll be telling us that ending our declarations with a semicolon is also incorrect.
Nice article, to the point, and explained in, I can’t resist, layman’s terms.
You really should try Joda Time ( http://joda-time.sf.net ), it is more intuitive to use
Yep been there done that, if I remember correctly I wasted a good afternoon on the problem.
Evo i mene konačno :-).
Malo ću prekršiti etiketu i pisati na hrvatskom. Kad sam pročitao post prije sedam dana, jedna od prvih reakcija je bila “paaa, možda bi bilo bolje pisati na hrvatskom :-)”.
Međutim, engleski definitivno ima svojih prednosti, što se najbolje vidi po broju komentara.
Inače, samo postu (i kasnijim komentarima) nemam što dodati, osim što bih ukazao na jedan misleading citat u postu.
Naime, na početku se kaže:
“TimeStamp is a composite of a java.util.Date …”
što bi nekoga moglo navesti da pomisli da TimeStamp ima referencu (dakle da sadrži) Date, dok je u stvarnosti TimeStamp IZVEDEN iz klase Date (što je razvidno i iz kasnije diskusije, čak i ukoliko se ne pogleda sama definicija klase TimeStamp).
Suma sumarum - izvrstan post, and keep doing good work :-)
I was also saying that equals should be symmetric. With proper punctuation:
“That eqaual scenario in which d.equals(ts) is true, but ts.equals(d) is not, is even against the …” ;-)
[...] Calyx blog: TimeStamp vs. Date, polymorphism strikes back! Jan Kuenstler [...]