Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

java.util.Date equals() doesn't seem to work as expected

Tags:

java

date

Problem

I have a Map<Date, Foo>, and a list of objects from the database with an effectiveDate property, and I want to check to see if the Date keys in my map are equal to any of the effectiveDates in the database - if so, do stuff with Foo.

The code looks something like this:

for (Bar bar : databaseBars) {
  Foo foo = new Foo();
  if (dateMap.containsKey(bar.getEffectiveDate()) {
    foo = dateMap.get(bar.getEffectiveDate());
  }
  // do stuff with foo and bar
}

However, the dateMap.containsKey call always returns false, even though I'm sure it's sometimes there.

Investigation

As a sanity check, I've printed out the long values of the dates, as well as the results of an equals() call and a compareTo() call:

for (Date keyDate : dateMap.keySet()) {
  if (keyDate == null) {
    continue; // make things simpler for now
  }

  Date effDate = bar.getEffectiveDate();

  String template = "keyDate: %d; effDate: %d; equals: %b; compareTo: %d\n";

  System.out.printf(template, keyDate.getTime(), effDate.getTime(), effDate.equals(keyDate), effDate.compareTo(keyDate));
}

The results:

keyDate: 1388534400000; effDate: 1388534400000; equals: false; compareTo: 0
keyDate: 1420070400000; effDate: 1388534400000; equals: false; compareTo: -1
keyDate: 1388534400000; effDate: 1420070400000; equals: false; compareTo: 1
keyDate: 1420070400000; effDate: 1420070400000; equals: false; compareTo: 0
keyDate: 1388534400000; effDate: 1388534400000; equals: false; compareTo: 0
keyDate: 1420070400000; effDate: 1388534400000; equals: false; compareTo: -1
keyDate: 1388534400000; effDate: 1420070400000; equals: false; compareTo: 1
keyDate: 1420070400000; effDate: 1420070400000; equals: false; compareTo: 0
keyDate: 1388534400000; effDate: 1388534400000; equals: false; compareTo: 0
keyDate: 1420070400000; effDate: 1388534400000; equals: false; compareTo: -1
keyDate: 1388534400000; effDate: 1420070400000; equals: false; compareTo: 1
keyDate: 1420070400000; effDate: 1420070400000; equals: false; compareTo: 0

Question

1) Shouldn't equals and compareTo agree? (I assume the implementation of java.util.Date at least should try to follow the recommendation of java.lang.Comparable).

2) The Date#equals doc says this:

Thus, two Date objects are equal if and only if the getTime method returns the same long value for both.

...Looks like the getTime method returns the same long value for both of these dates, yet equal returns false. Any ideas why this might be happening? I've searched high and low, but I haven't found anyone describing the same problem.

P.S. I'm stuck using java.util.Date. Please don't just recommend JodaTime.

P.P.S. I realize I could just change the structure of this code and probably get it working. But this should work, and I don't want to just work around it, unless it's a known issue or something. It just seems wrong.

like image 724
ethanbustad Avatar asked Dec 26 '14 20:12

ethanbustad


People also ask

What is wrong with Java Util date?

util. Date (just Date from now on) is a terrible type, which explains why so much of it was deprecated in Java 1.1 (but is still being used, unfortunately). Design flaws include: Its name is misleading: it doesn't represent a Date , it represents an instant in time.

How do you equal a date in Java?

The equals() method of Java Date class checks if two Dates are equal, based on millisecond difference. Parameters: The function accepts a single parameter obj which specifies the object to be compared with. Return Value: The function gives 2 return values specified below: true if the objects are equal.

Is Java Util date deprecated?

Open the source code of java. util. Date class and you would see almost all the constructors & methods of this class is deprecated.

How do I use util date?

Date. getTime() method results in count of milliseconds of the argumented date, referencing January 1, 1970, 00:00:00 GMT. Syntax: public long getTime() Result: milliseconds of the argumented date, referencing January 1, 1970, 00:00:00 GMT.


3 Answers

As Mureinik hinted at and Sotirios Delimanolis pointed out more specifically, the problem here is with the implementation of java.util.Date.

java.util.Date is extended by 3 classes in the java.sql package, all of which seem to do similar things and whose distinction in java is not at all clear (seems like the reason for their existence is simply to make java classes which align more accurately to SQL datatypes) - for more information on their differences, check out this very detailed answer.

Now, in what seems like a serious design flaw, someone decided to make equals() asymmetric with java.sql.Timestamp - that is, timestamp.equals(date) could return false even if date.equals(timestamp) returns true. Great idea.

I wrote a few lines to see which java.sql classes demonstrate this ridiculous property - apparently it's just Timestamp. This code:

java.util.Date utilDate = new java.util.Date();

java.sql.Date sqlDate = new java.sql.Date(utilDate.getTime());

System.out.println("sqlDate equals utilDate:\t" + sqlDate.equals(utilDate));
System.out.println("utilDate equals sqlDate:\t" + utilDate.equals(sqlDate));

java.sql.Time time = new java.sql.Time(utilDate.getTime());

System.out.println("time equals utilDate:\t\t" + time.equals(utilDate));
System.out.println("utilDate equals time:\t\t" + utilDate.equals(time));

java.sql.Timestamp timestamp = new java.sql.Timestamp(utilDate.getTime());

System.out.println("timestamp equals utilDate:\t" + timestamp.equals(utilDate));
System.out.println("utilDate equals timestamp:\t" + utilDate.equals(timestamp));

Yields this:

sqlDate equals utilDate:    true
utilDate equals sqlDate:    true
time equals utilDate:       true
utilDate equals time:       true
timestamp equals utilDate:  false
utilDate equals timestamp:  true

Since java.util.HashMap uses parameter.equals(key) in it's implementation of containsKey() (rather than key.equals(parameter)), this one strange result shows up in the given situation.

So, how to get around this?

1) Use a Long key in the map rather than a Date (as Mureinik noted) - since java.util.Date and java.util.Timestamp return the same value from getTime(), it shouldn't matter which implementation you're using, the key will be the same. This way does seem like the simplest.

2) Standardize the date object before using it in the map. This way requires a tiny bit more work, but to me seems more desirable as it's more clear what the map is - a bunch of Foo each stored against a moment in time. This is the way I ended up using, with the following method:

public Date getStandardizedDate(Date date) {
  return new Date(date.getTime());
}

It takes an extra method call (and kind of a ridiculous one at that), but to me the increased readability of the code involving the Map<Date, Foo> is worth it.

like image 155
2 revs Avatar answered Oct 18 '22 13:10

2 revs


Part 1: "Shouldn't equals agree with compareTo?*

No; compareTo should agree with equals, but the reverse is irrelevant.

compareTo is about sorting order. equals is about equality. Consider race cars, that may be sorted by fastest lap time in practice to determine starting position. Equal lap times does not mean they are the same car.

Part 2: Equal dates.

Database calls will return a java.sql.Date, which although it is assignable to java.util.Dare, because it extends that, will not be equal, because the class is different.

A work around may be:

java.util.Date test;
java.sql.Date date;
if (date.equals(new java.sql.Date(test.getTime()))
like image 20
Bohemian Avatar answered Oct 18 '22 14:10

Bohemian


A Date object returned from a database would probably be a java.sql.Timestamp, which cannot be equal to a java.util.Date object. I'd just take the long returned from getTime(), and use that as a key in your HashMap.

like image 35
Mureinik Avatar answered Oct 18 '22 14:10

Mureinik