정밀도 수준이 다른 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 2015

61

또 다른 해결 방법은 다음과 같습니다.

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

4
분산 비교에 +1하지만 절대 분산은 고려하지 않습니다 (예 : date1이 date2 이후이면 어떻게됩니까?)
Ophidian 2010

13
나는 보통 Math.abs ()로 감싸는 비슷한 접근 방식을 취합니다
parxier

60

이를 도와주는 라이브러리가 있습니다.

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부터 Java 8 날짜 / 시간 API를 사용하는 경우 AssertJisCloseTo어설 션을 추가했습니다 .

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

레거시 자바 날짜에서도 작동합니다.

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

이것이 내가 찾던 솔루션입니다 :)
geoaxis

1
덕분에 시간이 많이 절약되었습니다!
Robert Beltran 2013

DateUtils.round를 사용하지 않는 이유는 무엇입니까?
domi aug.

1
라운드도 작동합니다. 반올림 또는 내림되지만 자르기는 항상 내려갑니다. 당 문서 , 라운드 일광 절약 시간을 처리합니다.
Dan Watt

내가 가진 동일한 문제를 가지고 java.sql.Timestamps와는 DateUtils.truncate(...)자바 나를 위해 일한 8. 나의 특별한 경우 내가 저장되었던 하나에 메모리 타임 스탬프를 비교 그래서, 두 번째보다 미세한 입자를 절약 지원하지 않는 데이터베이스 기술을 포함 데이터베이스에서 검색됩니다. 메모리 내 타임 스탬프는 데이터베이스에서 읽은 타임 스탬프보다 정밀도가 더 큽니다.
Kent Bull

6

다음과 같이 할 수 있습니다.

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

문자열 비교가 필요하지 않습니다.


"/"대 "%"를 의미한다고 생각하십니까? 이것은 임의 정밀도 IMHO와 관련하여 지저분 해집니다. 그래도 좋은 지적입니다.
Michael Easter

이런! 잘 잡았습니다. 나는 정밀도가 문제라고 생각하지 않습니다. Date.getTime ()은 항상 epoch 이후 ms의 긴 시간을 반환합니다.
Seth

1
한 값이 3.999 초이고 다른 값이 4.000이면 실패합니다. 다시 말해, 때로는 최대 1 초의 차이를 견딜 수 있고 때로는 2ms 차이로 실패 할 수도 있습니다.
David Balažic 2014-06-13

6

JUnit에서 다음과 같이 두 가지 assert 메서드를 프로그래밍 할 수 있습니다.

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)만듭니다. 이렇게하면 모든 JUnit assert*메소드 와 동일하게 작동합니다 .
Joachim Sauer

동의합니다. 나는 코드를 실행하고 싶었고 JUnit이 손에 없었습니다.
Michael Easter

1
글로벌 날짜 포맷터에주의하십시오. 스레드로부터 안전하지 않습니다. 이 코드의 문제는 아니지만 가지고있는 것은 나쁜 습관입니다.
itsadok 2009

1
두 Date 객체가 1 초 미만의 차이가 있지만 두 번째 임계 값을 초과하는 경우는 처리하지 않습니다.
Ophidian 2010

3

이것은 당신이 신경 쓰지 않는 분산이 당신이 체크하고있는 값에 대한 임계 값을 넘는 경계 케이스 때문에 나타나는 것보다 실제로 더 어려운 문제입니다. 예를 들어 밀리 초 차이가 1 초 미만이지만 두 타임 스탬프가 두 번째 임계 값, 분 임계 값 또는 시간 임계 값을 교차합니다. 따라서 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를 사용하면 선택한 정밀도에 따라 날짜를 테스트 하기위한 매처 를 구현할 수도 있습니다 . 이 예에서 matcher는 문자열 형식 표현식을 매개 변수로 사용합니다. 이 예제에서는 코드가 더 짧지 않습니다. 그러나 matcher 클래스는 재사용 될 수 있습니다. 설명하는 이름을 지정하면 테스트의 의도를 우아한 방식으로 문서화 할 수 있습니다.

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

Joda-Time에 대해 AssertJ 어설 션 사용 ( 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");
Dan Watt

1

Joda를 사용하는 경우 Fest Joda Time을 사용할 수 있습니다 .


3
이것이 어떻게 구현되어야하는지에 대한 더 많은 정보를 제공 할 수 있습니까? 그렇지 않으면 주석으로 변환해야합니다.
Hugo Dozois 2013

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의 특정 getter 필드가 더 이상 사용되지 않는다는 것입니다. 캘린더를 사용하여 동일한 작업을 수행하는 것이 좋습니다.
kfox

아, 이러한 메서드는 더 이상 사용되지 않습니다. 대신 Calendar 개체를 변환하고 비교하는 대체 코드로 내 대답을 업데이트했습니다.
Oliver Hernandez

1

JUnit에는 double을 비교하고 얼마나 근접해야하는지 지정하기위한 내장 어설 션이 있습니다. 이 경우 델타는 날짜가 동일한 것으로 간주하는 밀리 초 이내입니다. 이 솔루션에는 경계 조건이없고 절대 분산을 측정하며 정밀도를 쉽게 지정할 수 있으며 추가 라이브러리 나 코드를 작성할 필요가 없습니다.

    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이 아닌 100.0이어야합니다 (또는 double로 캐스트해야 함).


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

다음과 같이 작동 할 수 있습니다.

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

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

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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.