web-dev-qa-db-fra.com

Comparer les objets Date avec différents niveaux de précision

J'ai un test JUnit qui échoue car les millisecondes sont différentes. Dans ce cas, je me moque des millisecondes. Comment puis-je changer la précision de l'assertion pour ignorer les millisecondes (ou toute précision que j'aimerais définir)?

Exemple d’affirmation manquante que je voudrais transmettre:

Date dateOne = new Date();
dateOne.setTime(61202516585000L);
Date dateTwo = new Date();
dateTwo.setTime(61202516585123L);
assertEquals(dateOne, dateTwo);
65
brainimus

Utilisez un objet DateFormat avec un format affichant uniquement les parties que vous souhaitez faire correspondre et effectuez une opération assertEquals() sur les chaînes obtenues. Vous pouvez également intégrer cela dans votre propre méthode assertDatesAlmostEqual().

22
Joachim Sauer

Encore une autre solution de contournement, je le ferais comme ceci:

assertTrue("Dates aren't close enough to each other!", (date2.getTime() - date1.getTime()) < 1000);
53
Esko

Il y a des bibliothèques qui aident avec ceci:

Apache commons-lang

Si vous avez Apache commons-lang sur votre chemin de classe, vous pouvez utiliser DateUtils.truncate pour tronquer les dates dans certains champs.

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

Il y a un raccourci pour cela:

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

Notez que 12: 00: 00.001 et 11: 59: 00.999 seraient tronqués à des valeurs différentes, ce qui pourrait ne pas être idéal. Pour cela, il y a rond:

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

AssertJ

A partir de la version 3.7.0, AssertJ a ajouté une isCloseTo assertions, si vous utilisez l'API Java 8 Date/Time.

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));

Il fonctionne également avec les dates Java héritées:

Date d1 = new Date();
Date d2 = new Date();
assertThat(d1).isCloseTo(d2, within(100, ChronoUnit.MILLIS).getValue());
48
Dan Watt

Vous pouvez faire quelque chose comme ça:

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

Aucune comparaison de chaîne nécessaire.

6
Seth

Dans JUnit, vous pouvez programmer deux méthodes d'assertion, comme ceci:

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);
  }
}
5

Je ne sais pas s'il y a un support dans JUnit, mais une façon de le faire:

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!"); }
    }
}
4
Michael Easter

Il s’agit en réalité d’un problème plus complexe qu’il ne le semble en raison des cas limites où la variance qui vous est indifférent dépasse le seuil d’une valeur que vous vérifiez. par exemple. la différence en millisecondes est inférieure à une seconde mais les deux horodatages dépassent le deuxième seuil, le seuil des minutes ou le seuil des heures. Cela rend toute approche DateFormat intrinsèquement sujette aux erreurs.

Au lieu de cela, je suggérerais de comparer les horodatages en millisecondes réels et de fournir un delta de variance indiquant ce que vous considérez comme une différence acceptable entre les deux objets de date. Voici un exemple trop verbeux:

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);
}
3
Ophidian

utiliser les assertions AssertJ pour 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()));

le message d'échec du test est plus lisible

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.
2
ysl

En utilisant JUnit 4, vous pouvez également implémenter un matcher pour tester les dates selon la précision que vous avez choisie. Dans cet exemple, Matcher prend une expression de format de chaîne en tant que paramètre. Le code n'est pas plus court pour cet exemple. Cependant, la classe Matcher peut être réutilisée. et si vous lui donnez un nom descriptif, vous pouvez documenter l’intention du test de manière élégante. 

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

Si vous utilisiez Joda, vous pouvez utiliser Fest Joda Time .

1
kdombeck

JUnit a une assertion intégrée pour comparer les doublons et spécifier leur proximité. Dans ce cas, le delta correspond au nombre de millisecondes que vous considérez comme des dates équivalentes. Cette solution n'a pas de conditions aux limites, mesure la variance absolue, peut facilement spécifier une précision et ne nécessite aucune bibliothèque ou code supplémentaire pour être écrite.

    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);

Remarque Il doit être 100.0 au lieu de 100 (ou une conversion pour doubler est nécessaire) pour le forcer à les comparer en double.

1
w25r

Il suffit de comparer les parties de date que vous souhaitez comparer:

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));
1
Oliver Hernandez

Convertissez les dates en chaîne à l'aide de SimpleDateFromat, spécifiez dans le constructeur les champs de date/heure requis et comparez les valeurs de chaîne:

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

Vous pouvez choisir le niveau de précision souhaité lors de la comparaison de dates, par exemple:

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);
0
Ognjen Stanić

J'ai fait un petit cours qui pourrait être utile pour certains googlers qui finissent ici: https://stackoverflow.com/a/37168645/5930242

0
FredBoutin

Voici une fonction utilitaire qui a fait le travail pour moi.

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

0
abasar

Quelque chose comme ça pourrait marcher:

assertEquals(new SimpleDateFormat("dd MMM yyyy").format(dateOne),
                   new SimpleDateFormat("dd MMM yyyy").format(dateTwo));
0
Bino Manjasseril

Au lieu d'utiliser directement new Date, vous pouvez créer un petit collaborateur que vous pourrez simuler lors de votre test:

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

Créez un membre DateBuilder et modifiez les appels de new Date à 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();
    }
}

La méthode principale produira:

Dates are the same: false

Dans le test, vous pouvez injecter un stub de DateBuilder et le laisser renvoyer n'importe quelle valeur. Par exemple, avec Mockito ou une classe anonyme qui remplace 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();
    }
}
0
timomeinen