Порівняйте об'єкти Date з різними рівнями точності


81

У мене є тест JUnit, який не вдається, оскільки мілісекунди відрізняються. У цьому випадку мене не хвилюють мілісекунди. Як я можу змінити точність твердження, щоб ігнорувати мілісекунди (або будь-яку точність, яку я хотів би встановити)?

Приклад невдалого твердження, яке я хотів би передати:

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

Відповіді:


22

Використовуйте DateFormatоб’єкт у форматі, який показує лише ті частини, яким ви хочете збігатися, і виконуйте assertEquals()на отриманих рядках. Ви також можете легко обернути це своїм assertDatesAlmostEqual()методом.


15
Не враховує випадок мілісекунд різниці над другою межею, 10.000 та 09.999 були б різними.
scarba05

61

Ще одне обхідне рішення, я б зробив це так:

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

4
+1 для порівняння дисперсії, але не враховує абсолютної дисперсії (наприклад, що, якщо date1 після дати2?)
Офідіан

13
Зазвичай я застосовую подібний підхід, просто обгортаючи його Math.abs ()
parxier

60

Є бібліотеки, які допомагають у цьому:

Apache commons-lang

Якщо у вас є Apache commons-lang на вашому шляху DateUtils.truncateдо класу , ви можете використати для усічення дат у якомусь полі.

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

Для цього є скорочення:

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

Зверніть увагу, що 12: 00: 00.001 та 11: 59: 00.999 будуть усікатися до різних значень, тому це може бути не ідеальним. Для цього є круглий:

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

AssertJ

Починаючи з версії 3.7.0, AssertJ додав isCloseToтвердження, якщо ви використовуєте 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));

Він також працює зі застарілими датами Java:

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

це рішення я шукав :)
geoaxis

1
Дякую, це заощадило мені купу часу!
Роберт Белтран,

Чому б не використовувати DateUtils.round?
domi

1
Круглий також спрацював би. Він буде округлятися вгору або вниз, тоді як усікання завжди буде знижуватися. Згідно з документами , раунд також обробляє літній час.
Dan Watt,

У мене була така ж проблема з java.sql.Timestampsі DateUtils.truncate(...)працював для мене в Java 8. Мій окремий випадок включав в себе технологію бази даних , яка не підтримує збереження яких - або дрібне зерно , ніж другий, так що я порівнював в оперативній пам'яті Відмітка часу на той , який був збережений і отримано з бази даних. Відмітка часу в пам'яті мала більшу точність, ніж відмітка часу, яка зчитувалася з бази даних.
Кент Булл,

6

Ви можете зробити щось подібне:

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

Порівняння рядків не потрібно.


Я думаю, ви мали на увазі "/" проти "%"? Це стає брудним щодо довільної точності, IMHO. Хороший момент, хоча.
Михайло Пасха

Ой! Хороший улов. Я не думаю, що точність є проблемою. Date.getTime () завжди повертає довжину мс з епохи.
Сет

1
Це не вдасться, якщо одне значення становить 3,999 секунди, а друге - 4000. Іншими словами, іноді це допускає різницю до секунд, іноді не вдається через різницю в 2 мс.
Девід Балажич

6

У JUnit ви можете запрограмувати два методи затвердження, наприклад:

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

4

Я не знаю, чи є підтримка в JUnit, але один із способів це зробити:

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

Якщо ви викликаєте метод, assertEqualDatesя б зробив його тип повернення voidі зробив останній рядок assertEquals(d1, d2). Таким чином, він поводився б так само, як і всі assert*методи JUnit .
Йоахім Зауер

Домовились. Я хотів запустити код і не мав під рукою JUnit.
Михайло Пасха

1
Будьте обережні щодо глобальних форматорів дати. Вони не захищені від ниток. Це не проблема з цим кодом, але це шкідлива звичка.
ісадок

1
Це не обробляє випадок, коли два об'єкти Date мають різницю в секунду, але вони перетинають другий поріг.
Офідіан,

3

Це насправді складніша проблема, ніж представляється через граничні випадки, коли дисперсія, про яку ви не дбаєте, перетинає поріг для значення, яке ви перевіряєте. наприклад, різниця в мілісекундах менше секунди, але дві позначки часу перетинають другий поріг, або хвилину, або годину. Це робить будь-який підхід DateFormat по своїй суті схильним до помилок.

Натомість я б запропонував порівняти фактичні позначки часу в мілісекундах та надати дельту дисперсії, яка вказує, яку ви вважаєте допустимою різницею між двома об’єктами дати. Наводиться надто багатослівний приклад:

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

2

Використовуючи JUnit 4, ви також можете застосувати збіжник для тестування дат відповідно до обраної вами точності. У цьому прикладі збіжник приймає як параметр вираз формату рядка. Код для цього прикладу не є коротшим. Однак клас зіставлення може бути використаний повторно; і якщо ви дасте йому описуючу назву, ви можете елегантно задокументувати намір за допомогою тесту.

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

використовувати твердження AssertJ для 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()));

повідомлення про невдалий тест є більш читабельним

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.

1
AssertJ також працює на java.util.date:assertThat(new Date(2016 - 1900, 0, 1,12,13,14)).isEqualToIgnoringMillis("2016-01-01T12:13:14");
Ден Ватт

1

Якби ви використовували Joda, ви могли б використовувати Fest Joda Time .


3
чи не могли б ви надати більше інформації про те, як це слід реалізувати? В іншому випадку це слід перетворити на коментар.
Hugo Dozois

1

Просто порівняйте частини дати, які вас цікавлять для порівняння:

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

Мені подобається такий підхід набагато краще, ніж використання форматування дати. Єдина проблема полягає в тому, що конкретні поля геттера в Date застаріли. Краще використовувати Календар, щоб робити те саме.
kfox

Ах, гарно зазначити, що ці методи застаріли. Я оновив свою відповідь альтернативним кодом, щоб замість цього перетворити та порівняти об’єкти Календаря.
Олівер Ернандес,

1

JUnit має вбудоване твердження для порівняння подвійних та уточнення того, наскільки вони повинні бути близькими. У цьому випадку дельта знаходиться в межах того, скільки мілісекунд ви вважаєте датами еквівалентними. Це рішення не має граничних умов, вимірює абсолютну дисперсію, може легко визначити точність і не вимагає написання додаткових бібліотек або коду.

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

Примітка. Це повинно бути 100,0 замість 100 (або потрібно кидок для подвоєння), щоб змусити його порівнювати їх як подвійні.


1

Ви можете вибрати рівень точності для порівняння дат, наприклад:

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

Замість того, щоб використовувати new Dateбезпосередньо, ви можете створити невеликого співавтора, з якого ви можете висміятись у своєму тесті:

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

Створіть учасника DateBuilder і змініть виклики з 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();
    }
}

Основний метод дозволить отримати:

Dates are the same: false

У тесті ви можете ввести заглушку DateBuilderта дозволити їй повернути будь-яке значення, яке вам подобається. Наприклад, з Mockito або анонімним класом, який замінює 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

Перетворіть дати в String за допомогою SimpleDateFromat, вкажіть у конструкторі необхідні поля дати / часу та порівняйте значення рядків:

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


0

Ось функція утиліти, яка зробила цю роботу за мене.

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


-1

я перекидаю об'єкти на java.util.Date і порівнюю

assertEquals((Date)timestamp1,(Date)timestamp2);

Це призведе до невдалого затвердження через точність.
TechCrunch
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.