Compare Date objects with different levels of precision

Viewed 97176

I have a JUnit test that fails because the milliseconds are different. In this case I don't care about the milliseconds. How can I change the precision of the assert to ignore milliseconds (or any precision I would like it set to)?

Example of a failing assert that I would like to pass:

Date dateOne = new Date();
dateOne.setTime(61202516585000L);
Date dateTwo = new Date();
dateTwo.setTime(61202516585123L);
assertEquals(dateOne, dateTwo);
21 Answers

With AssertJ you could provide a custom comparator what is especially handy if you are comparing entire object structures and not single values so that other methods like isEqualToIgnoringMillis or isCloseTo are not practical.

assertThat(thing)
  .usingRecursiveComparison()
  .withComparatorForType(
      (a, b) -> a.truncatedTo(ChronoUnit.MILLIS).compareTo(b.truncatedTo(ChronoUnit.MILLIS)),
      OffsetDateTime.class
  )

You can chose which precision level you want when comparing dates, e.g.:

LocalDateTime now = LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS);
// e.g. in MySQL db "timestamp" is without fractional seconds precision (just up to seconds precision)
assertEquals(myTimestamp, now);

Using JUnit 4 you could also implement a matcher for testing dates according to your chosen precision. In this example the matcher takes a string format expression as a parameter. The code is not any shorter for this example. However the matcher class may be reused; and if you give it a describing name you can document the intention with the test in an elegant way.

import static org.junit.Assert.assertThat;
// further imports from org.junit. and org.hamcrest.

@Test
public void testAddEventsToBaby() {
    Date referenceDate = new Date();
    // Do something..
    Date testDate = new Date();

    //assertThat(referenceDate, equalTo(testDate)); // Test on equal could fail; it is a race condition
    assertThat(referenceDate, sameCalendarDay(testDate, "yyyy MM dd"));
}

public static Matcher<Date> sameCalendarDay(final Object testValue, final String dateFormat){

    final SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);

    return new BaseMatcher<Date>() {

        protected Object theTestValue = testValue;


        public boolean matches(Object theExpected) {
            return formatter.format(theExpected).equals(formatter.format(theTestValue));
        }

        public void describeTo(Description description) {
            description.appendText(theTestValue.toString());
        }
    };
}

JUnit has a built in assertion for comparing doubles, and specifying how close they need to be. In this case, the delta is within how many milliseconds you consider dates equivalent. This solution has no boundary conditions, measures absolute variance, can easily specify precision, and requires no additional libraries or code to be written.

    Date dateOne = new Date();
    dateOne.setTime(61202516585000L);
    Date dateTwo = new Date();
    dateTwo.setTime(61202516585123L);
    // this line passes correctly 
    Assert.assertEquals(dateOne.getTime(), dateTwo.getTime(), 500.0);
    // this line fails correctly
    Assert.assertEquals(dateOne.getTime(), dateTwo.getTime(), 100.0);

Note It must be 100.0 instead of 100 (or a cast to double is needed) to force it to compare them as doubles.

Here is a utility function that did the job for me.

    private boolean isEqual(Date d1, Date d2){
        return d1.toLocalDate().equals(d2.toLocalDate());
    }

You can use isEqualToIgnoringSeconds method to ignore seconds and compare only by minutes:

Date d1 = new Date();
Thread.sleep(10000);
Date d2 = new Date();
assertThat(d1).isEqualToIgnoringSeconds(d2); // true
Related