90

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);
brainimus
  • 10,586
  • 12
  • 42
  • 64

21 Answers21

82

There are libraries that help with this:

Apache commons-lang

If you have Apache commons-lang on your classpath, you can use DateUtils.truncate to truncate the dates to some field.

assertEquals(DateUtils.truncate(date1,Calendar.SECOND),
             DateUtils.truncate(date2,Calendar.SECOND));

There is a shorthand for this:

assertTrue(DateUtils.truncatedEquals(date1,date2,Calendar.SECOND));

Note that 12:00:00.001 and 11:59:00.999 would truncate to different values, so this might not be ideal. For that, there is round:

assertEquals(DateUtils.round(date1,Calendar.SECOND),
             DateUtils.round(date2,Calendar.SECOND));

AssertJ

Starting with version 3.7.0, AssertJ added an isCloseTo assertions, if you are using the Java 8 Date / Time API.

LocalTime _07_10 = LocalTime.of(7, 10);
LocalTime _07_42 = LocalTime.of(7, 42);
assertThat(_07_10).isCloseTo(_07_42, within(1, ChronoUnit.HOURS));
assertThat(_07_10).isCloseTo(_07_42, within(32, ChronoUnit.MINUTES));

It also works with legacy java Dates as well:

Date d1 = new Date();
Date d2 = new Date();
assertThat(d1).isCloseTo(d2, within(100, ChronoUnit.MILLIS).getValue());
Dan Watt
  • 1,367
  • 2
  • 12
  • 18
  • this is the solution I was looking for :) – geoaxis Aug 22 '13 at 11:02
  • 1
    Thanks this saved me a ton of time! – Robert Beltran Nov 26 '13 at 18:00
  • Why not use DateUtils.round? – domi Aug 08 '14 at 09:48
  • 2
    Round would work as well. It will round up or down, whereas truncate will always go down. Per the [docs](https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/lang/time/DateUtils.html), round also handles daylight savings time. – Dan Watt Mar 16 '15 at 20:24
  • 1
    I had the same issue with `java.sql.Timestamps` and the `DateUtils.truncate(...)` worked for me in Java 8. My particular case included a database technology that did not support saving any finer grain than a second, so I was comparing an in-memory Timestamp to one that had been saved and retrieved from a database. The in-memory Timestamp had greater precision than the Timestamp that as read from the database. – Kent Bull Apr 11 '16 at 14:40
  • 1
    The assertJ and round() approaches are the way to go. Good posting full code. – oligofren Mar 08 '21 at 08:38
  • For anyone working with `java.time.Instant`, there is `truncatedTo`, e.g: `startTime.truncatedTo(ChronoUnit.MICROS)`, then just perform your assertion on the truncated value as above - no need for any external libs. – slugmandrew Feb 14 '23 at 13:13
72

Yet another workaround, I'd do it like this:

assertTrue("Dates aren't close enough to each other!", (date2.getTime() - date1.getTime()) < 1000);
Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
Esko
  • 29,022
  • 11
  • 55
  • 82
25

Use a DateFormat object with a format that shows only the parts you want to match and do an assertEquals() on the resulting Strings. You can also easily wrap that in your own assertDatesAlmostEqual() method.

Joachim Sauer
  • 302,674
  • 57
  • 556
  • 614
  • 22
    Doesn't handle the case of a millisecond difference over a second boundary, 10.000 and 09.999 would be different. – scarba05 Nov 27 '15 at 09:41
11

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
  )
deamon
  • 89,107
  • 111
  • 320
  • 448
  • I had to also adjust the offset to make the test reliable. `(a, b) -> a.truncatedTo(ChronoUnit.MILLIS).withOffsetSameInstant(ZoneOffset.UTC).compareTo(b.truncatedTo(ChronoUnit.MILLIS).withOffsetSameInstant(ZoneOffset.UTC)),` – Faron Jan 30 '22 at 15:18
8

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);
Ognjen Stanić
  • 505
  • 8
  • 17
  • This IMHO should be the accepted answer. No extra libraries involved, no additional rounding code needed, can easily be modified to support any precision level. – francesco foresti Dec 05 '22 at 08:16
7

You could do something like this:

assertTrue((date1.getTime()/1000) == (date2.getTime()/1000));

No String comparisons needed.

Seth
  • 5,596
  • 8
  • 42
  • 56
  • I think you meant "/" versus "%"? This gets messy regarding arbitrary precision, IMHO. Good point though. – Michael Easter Nov 04 '09 at 04:29
  • Whoops! Good catch. I don't think precision is an issue though. Date.getTime() always returns a long of ms since the epoch. – Seth Nov 04 '09 at 06:02
  • 2
    This will fail if one value is 3.999 seconds and the other 4.000. Inother words, sometimes it will tolerate a difference up to a seconds, sometimes it will fail for a 2 ms difference. – David Balažic Jun 13 '14 at 16:44
6

In JUnit you can program two assert methods, like this:

public class MyTest {
  @Test
  public void test() {
    ...
    assertEqualDates(expectedDateObject, resultDate);

    // somewhat more confortable:
    assertEqualDates("01/01/2012", anotherResultDate);
  }

  private static final String DATE_PATTERN = "dd/MM/yyyy";

  private static void assertEqualDates(String expected, Date value) {
      DateFormat formatter = new SimpleDateFormat(DATE_PATTERN);
      String strValue = formatter.format(value);
      assertEquals(expected, strValue);
  }

  private static void assertEqualDates(Date expected, Date value) {
    DateFormat formatter = new SimpleDateFormat(DATE_PATTERN);
    String strExpected = formatter.format(expected);
    String strValue = formatter.format(value);
    assertEquals(strExpected, strValue);
  }
}
Gabriel Belingueres
  • 2,945
  • 1
  • 24
  • 32
4

I don't know if there is support in JUnit, but one way to do it:

import java.text.SimpleDateFormat;
import java.util.Date;

public class Example {

    private static SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy HH:mm:ss");

    private static boolean assertEqualDates(Date date1, Date date2) {
        String d1 = formatter.format(date1);            
        String d2 = formatter.format(date2);            
        return d1.equals(d2);
    }    

    public static void main(String[] args) {
        Date date1 = new Date();
        Date date2 = new Date();

        if (assertEqualDates(date1,date2)) { System.out.println("true!"); }
    }
}
Michael Easter
  • 23,733
  • 7
  • 76
  • 107
  • If you call the method `assertEqualDates` then I'd make its return type `void` and make the last line `assertEquals(d1, d2)`. This way it would behave the same as all the JUnit `assert*` methods. – Joachim Sauer Nov 04 '09 at 00:36
  • Agreed. I wanted to run the code and didn't have JUnit at hand. – Michael Easter Nov 04 '09 at 03:34
  • 1
    Be wary of global date formatters. They are not thread-safe. It's not a problem with this code, but it's a bad habit to have. – itsadok Nov 04 '09 at 06:35
  • 2
    This doesn't handle the case where the two Date objects have a sub-second difference but they cross the second threshold. – Ophidian Apr 16 '10 at 15:37
3

This is actually a harder problem than it appears because of the boundary cases where the variance that you don't care about crosses a threshold for a value you are checking. e.g. the millisecond difference is less than a second but the two timestamps cross the second threshold, or the minute threshold, or the hour threshold. This makes any DateFormat approach inherently error-prone.

Instead, I would suggest comparing the actual millisecond timestamps and provide a variance delta indicating what you consider an acceptable difference between the two date objects. An overly verbose example follows:

public static void assertDateSimilar(Date expected, Date actual, long allowableVariance)
{
    long variance = Math.abs(allowableVariance);

    long millis = expected.getTime();
    long lowerBound = millis - allowableVariance;
    long upperBound = millis + allowableVariance;

    DateFormat df = DateFormat.getDateTimeInstance();

    boolean within = lowerBound <= actual.getTime() && actual.getTime() <= upperBound;
    assertTrue(MessageFormat.format("Expected {0} with variance of {1} but received {2}", df.format(expected), allowableVariance, df.format(actual)), within);
}
Ophidian
  • 9,775
  • 2
  • 29
  • 27
2

use AssertJ assertions for Joda-Time (http://joel-costigliola.github.io/assertj/assertj-joda-time.html)

import static org.assertj.jodatime.api.Assertions.assertThat;
import org.joda.time.DateTime;

assertThat(new DateTime(dateOne.getTime())).isEqualToIgnoringMillis(new DateTime(dateTwo.getTime()));

the test failing message is more readable

java.lang.AssertionError: 
Expecting:
  <2014-07-28T08:00:00.000+08:00>
to have same year, month, day, hour, minute and second as:
  <2014-07-28T08:10:00.000+08:00>
but had not.
ysl
  • 183
  • 2
  • 10
  • 1
    AssertJ also works for java.util.date: `assertThat(new Date(2016 - 1900, 0, 1,12,13,14)).isEqualToIgnoringMillis("2016-01-01T12:13:14");` – Dan Watt Oct 27 '16 at 13:59
2

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());
        }
    };
}
1

Just compare the date parts you're interested in comparing:

Date dateOne = new Date();
dateOne.setTime(61202516585000L);
Date dateTwo = new Date();
dateTwo.setTime(61202516585123L);

assertEquals(dateOne.getMonth(), dateTwo.getMonth());
assertEquals(dateOne.getDate(), dateTwo.getDate());
assertEquals(dateOne.getYear(), dateTwo.getYear());

// alternative to testing with deprecated methods in Date class
Calendar calOne = Calendar.getInstance();
Calendar calTwo = Calendar.getInstance();
calOne.setTime(dateOne);
calTwo.setTime(dateTwo);

assertEquals(calOne.get(Calendar.MONTH), calTwo.get(Calendar.MONTH));
assertEquals(calOne.get(Calendar.DATE), calTwo.get(Calendar.DATE));
assertEquals(calOne.get(Calendar.YEAR), calTwo.get(Calendar.YEAR));
  • I like this approach a lot better then using a date formatter. Only problem is that the specific getter fields in Date are deprecated. Better to use a Calendar to do the same thing. – kfox Jul 23 '13 at 17:11
  • Ah, good point to note that those methods are deprecated. I've updated my answer with the alternative code to convert and compare Calendar objects instead. – Oliver Hernandez Jul 30 '13 at 15:20
1

If you were using Joda you could use Fest Joda Time.

kdombeck
  • 11
  • 1
  • 3
    could you provide more information as how this should be implemented? Else this should be converted to a comment. – Hugo Dozois Feb 28 '13 at 19:27
1

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.

w25r
  • 882
  • 10
  • 15
1

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
Sarvar Nishonboyev
  • 12,262
  • 10
  • 69
  • 70
0

Instead of using new Date directly, you can create a small collaborator, which you can mock out in your test:

public class DateBuilder {
    public java.util.Date now() {
        return new java.util.Date();
    }
}

Create a DateBuilder member and change calls from new Date to dateBuilder.now()

import java.util.Date;

public class Demo {

    DateBuilder dateBuilder = new DateBuilder();

    public void run() throws InterruptedException {
        Date dateOne = dateBuilder.now();
        Thread.sleep(10);
        Date dateTwo = dateBuilder.now();
        System.out.println("Dates are the same: " + dateOne.equals(dateTwo));
    }

    public static void main(String[] args) throws InterruptedException {
        new Demo().run();
    }
}

The main method will produce:

Dates are the same: false

In the test you can inject a stub of DateBuilder and let it return any value you like. For example with Mockito or an anonymous class which overrides now():

public class DemoTest {

    @org.junit.Test
    public void testMockito() throws Exception {
        DateBuilder stub = org.mockito.Mockito.mock(DateBuilder.class);
        org.mockito.Mockito.when(stub.now()).thenReturn(new java.util.Date(42));

        Demo demo = new Demo();
        demo.dateBuilder = stub;
        demo.run();
    }

    @org.junit.Test
    public void testAnonymousClass() throws Exception {
        Demo demo = new Demo();
        demo.dateBuilder = new DateBuilder() {
            @Override
            public Date now() {
                return new Date(42);
            }
        };
        demo.run();
    }
}
timomeinen
  • 3,101
  • 3
  • 33
  • 46
0

Something like this might work:

assertEquals(new SimpleDateFormat("dd MMM yyyy").format(dateOne),
                   new SimpleDateFormat("dd MMM yyyy").format(dateTwo));
javanna
  • 59,145
  • 14
  • 144
  • 125
0

Convert the dates to String using SimpleDateFromat, specify in the constructor the required date/time fields and compare the string values:

SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String expectedDate = formatter.format(dateOne));
String dateToTest = formatter.format(dateTwo);
assertEquals(expectedDate, dateToTest);
Roberto
  • 4,524
  • 1
  • 38
  • 30
0

I did a small class that might be useful for some googlers that end up here : https://stackoverflow.com/a/37168645/5930242

Community
  • 1
  • 1
FredBoutin
  • 392
  • 3
  • 16
0

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

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

abasar
  • 1,659
  • 1
  • 12
  • 7
-2

i cast the objects to java.util.Date and compare

assertEquals((Date)timestamp1,(Date)timestamp2);
Grubhart
  • 1,106
  • 13
  • 19