Search code examples
javascalazoneddatetime

How to normalise ZonedDateTime so that .equals() works?


I have code, similar to this:

import java.time._

object app {
  def main (args :Array[String]) = {
    println("app started")

    // create two ZonedDateTime objects for 1st Jan 2018, 10am UTC
    // using separate methods
    val zdt1 = ZonedDateTime.of(2018, 1, 1, 10, 0, 0, 0, ZoneId.of("UTC"))
    val zdt2 = ZonedDateTime.parse("2018-01-01T10:00:00Z")

    println(s"COMPARING: $zdt1 and $zdt2")
    println("== check:      " + (zdt1 == zdt2))
    println(".equals check: " + (zdt1.equals(zdt2)))
    println(".isEqual check " + (zdt1.isEqual(zdt2)))

    println("app finished")
  }
}

Code available here: https://ideone.com/43zf8B

The issue:

  1. these ARE both typed ZonedDateTime objects
  2. they are equivalent according to the .isEqual() method..
  3. they are not equivalent according to .equals() method

However my test suite uses deep matching using beEquals operations against the classes these datetime instances are in, therefore I need a way to normalise them so that .equals() returns true.

how can I normalise them please?


Solution

  • If I create zdt1 with ZonedDateTime.of(2018, 1, 1, 10, 0, 0, 0, ZoneOffset.UTC), the two objects are equal under equals() (still not under == in Java).

    Apparently it’s not enough for the zones to be equivalent when their names are different. By using ZoneOffset.UTC for constructing the first ZonedDateTime, both will have the same time zone and will thus be equal. With my change, at least on my Mac, zdt1.getZone() == zdt2.getZone() now evaluates to true.

    As a more direct answer to your question, you may normalize your ZonedDateTime objects this way (Java syntax with semicolon, please translate yourself):

        zdt1 = zdt1.withZoneSameInstant(zdt1.getZone().normalized());
    

    Similarly for zdt2, of course. ZoneId.normalized() promises to return a ZoneOffset where possible, which it is in your case. So in your case it does make two objects that are equal under equals(). I’m not sure it will in all other cases.

    A safer way would be to have the comparison explicitly take care of different but equal time zones:

    zdt1.toInstant().equals(zdt2.toInstant())
            && zdt1.getZone().getRules().equals(zdt2.getZone().getRules())
    

    This evaluates to true with your two date-times from the question.

    BTW isEqual() compares the instants in time only, not the zones at all, which is why it didn’t care.