8

As you can see below, after serializing and unserializing, you get DateTime instances that are supposedly different:

scala> import org.joda.time.DateTime
import org.joda.time.DateTime

scala> val a = new DateTime()
a: org.joda.time.DateTime = 2014-01-08T19:00:08.883+02:00

scala> val b = DateTime.parse(a.toString())
b: org.joda.time.DateTime = 2014-01-08T19:00:08.883+02:00

scala> a == b
res0: Boolean = false

As per AbstractInstant's javadoc, equals "compares this object with the specified object for equality based on the millisecond instant, chronology and time zone." So this shouldn't be happening right? What am I missing?

Dominykas Mostauskis
  • 7,797
  • 3
  • 48
  • 67

4 Answers4

14

Here is the only right answer, found by own testing:

DateTime a = new DateTime(); // uses default time zone
System.out.println(a); // 2014-01-08T19:38:00.696+01:00

DateTime b = DateTime.parse(a.toString());
System.out.println(b); // 2014-01-08T19:38:00.696+01:00

System.out.println(a.getChronology()); // ISOChronology[Europe/Berlin]
System.out.println(b.getChronology()); // ISOChronology[+01:00]
System.out.println(a.equals(b)); // false!!!

I have assumed that in Scala the comparison by == indeed means comparison by equals() as @aris1348880 has stated in his comment, therefore I have replaced in the translation to java code the operator correspondingly.

So the cause of failed equals()-comparison is obvious: It is the time zone id which is not printed correctly in the toString()-method of DateTime object a. I consider it as a bug in JodaTime because toString() should always print the whole state of an immutable value object. By the way, in this detail old java.util.Date is even worse! Well, as work around you can use the format engine of JodaTime in order to print out correctly.

System.out.println(
    DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'['ZZZ']'").print(a));
// Output: 2014-01-08T19:38:00.696[Europe/Berlin]

To make the behaviour more clear:

The DateTime-constructor uses the default time zone (in my test Europe/Berlin). Its toString()-method prints the offset instead, not the real time zone (that is the problem).

The DateTime-parse()-method uses according to the JodaTime documentation ISODateTimeFormat#dateTimeParser() which again can only parse offsets, but in this code it was also feeded with just offset information, not with the real time zone, otherwise the parsing would have stopped with an exception.

In both cases instances of DateTime are created differently that is with different time zones / offsets and hence different chronologies. So the equals()-result is on first glance astonishing but understandable.

Meno Hochschild
  • 42,708
  • 7
  • 104
  • 126
  • 1
    It seems, to me, that the bug is with `parse` that isn't using the default time zone... but parsing a date coming out of a `toString` method is rather odd. – Jonathan Drapeau Jan 08 '14 at 19:01
  • 1
    @JonathanDrapeau No the parse method is correct because it reads the time zone string "+01:00" and treats it just as an offset, so far okay (time zone information in the text must have precedence compared with time zone setting in formatter object). But else I agree that the whole procedure is somehow odd. I would also not call it an "unserialization", just as formatting and reparsing. – Meno Hochschild Jan 08 '14 at 19:05
  • When using complex types like DateTime it's better to avoid to use generic way to build object. In the example the problem is with time zone but millis are the same so a.compareTo(b) == 0. Equality for a complex type is not always a straightforward concept as equality for the state of an object instance of such type. For example BigDecimal equality is not the same as compareTo (on value) because equality check value and scale while compareTo only value so new BigDecimal("1.0").equals(new BigDecimal("1.00") is false while new BigDecimal("1.0").compareTo(new BigDecimal("1.00") == 0. – Aris2World Jan 08 '14 at 21:40
  • Thanks for the insight. I suppose the problem is that Joda DateTime provides more detail than the ISO8601 format. I'll subclass DateTime and override `equals` to make the chronology comparison less precise. – Dominykas Mostauskis Jan 09 '14 at 11:10
  • 1
    I don't know if scala can do subclass magic, but at least in pure Java subclassing DateTime is not possible because the class is final. As far as I know the JodaTime project lead S. Colebourne has later regretted the design decision to have a [pluggable chronology](http://blog.joda.org/2009/11/why-jsr-310-isn-joda-time_4941.html). – Meno Hochschild Jan 09 '14 at 11:58
5

I opened an issue on GitHub, here is the author's response:

A long time ago I chose the toString format of DateTime, and I omitted to include the full state of the value type. As a result, two objects look like they are equal (by toString) but they are not. That was a mistake as it causes this kind of confusion. Unfortunately, it cannot be corrected.

The parsing behaviour is also unfortunate, as it focuses too much on the offset and not enough on the time-zone (as ISO-8601 does not handle time zone IDs). Again, its too late to make changes here.

Community
  • 1
  • 1
Dominykas Mostauskis
  • 7,797
  • 3
  • 48
  • 67
3

I haven't org.joda.time.DateTime but I just tried a test with java.util.Date.

You should try this:

val a = new DateTime()
val b = new DateTime(a.getMillis())
a == b

I also tried this:

import org.joda.time.DateTime;

public class TestDateTime {

    public static void main(String[] args) {
        DateTime a = new DateTime();
        System.out.println(a.toString());
        DateTime b = DateTime.parse(a.toString());
        System.out.println(b.toString());
        System.out.println(a.equals(b));
    }
}

and this is the output:

2014-01-08T19:05:52.182+01:00
2014-01-08T19:05:52.182+01:00
false

And the problem is that equals of DateTime fails because different time zone. I think it's wrong to assume equivalence of two DateTime instances created in such different way.

When you serialize and deserialize a java.util.Date the long (the wrapped value) is passed not a String.

Aris2World
  • 1,214
  • 12
  • 22
  • sorry why -2? is it for == instead of equals? Don't you know that with scala interpreter a == b means a.equals(b)? see http://stackoverflow.com/questions/7681161/whats-the-difference-between-and-equals-in-scala – Aris2World Jan 08 '14 at 17:53
  • 3
    My upvote for you to have proved that the cause is not the use of scala operator ==. – Meno Hochschild Jan 08 '14 at 18:54
  • The downsize of this method is that you no longer have a human-readable serialized date. – Sam Dufel Aug 09 '16 at 00:25
  • Sorry @SamDufel which method do you refer? I mean which method of DateTime: equals or toString or parse? – Aris2World Aug 09 '16 at 09:34
  • I think the problem is that toString and parse are not tied and so it's wrong to assume equivalence. It is only a problem of contract. I prefer to divide human-readability from serialization (when serialiaze and unserialize an object the two instances must be equal) – Aris2World Aug 09 '16 at 10:02
0

== will compare reference, equals() compares values. Try a.equals(b)

Check out this post: What is the difference between == vs equals() in Java?

Community
  • 1
  • 1
quinnjn
  • 623
  • 7
  • 10
  • 3
    After looking at scala boys comment about the meaning of == in scala this answer does not seem right. But even `equals()` produces false, as I have proved in my test, see my answer. – Meno Hochschild Jan 08 '14 at 18:52
  • That is true for java, whereas in scala `==()` which is a call to `equals()` compares values and [`x.eq(y)` checks whether `y` is a reference to `x`](http://stackoverflow.com/questions/6835578/scala-reference-equality). – Dominykas Mostauskis Jan 20 '14 at 08:08