So sánh các đối tượng Ngày với các mức độ chính xác khác nhau


81

Tôi có một bài kiểm tra JUnit không thành công vì mili giây khác nhau. Trong trường hợp này, tôi không quan tâm đến mili giây. Làm cách nào để thay đổi độ chính xác của xác nhận để bỏ qua mili giây (hoặc bất kỳ độ chính xác nào tôi muốn đặt thành)?

Ví dụ về khẳng định thất bại rằng tôi muốn vượt qua:

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

Câu trả lời:


22

Sử dụng một DateFormatđối tượng có định dạng chỉ hiển thị các phần bạn muốn khớp và thực hiện assertEquals()trên các Chuỗi kết quả. Bạn cũng có thể dễ dàng bọc nó theo assertDatesAlmostEqual()phương pháp của riêng bạn .


14
Không xử lý trường hợp chênh lệch mili giây trên ranh giới thứ hai, 10.000 và 09.999 sẽ khác nhau.
Scarba05

61

Tuy nhiên, một giải pháp khác, tôi sẽ làm như thế này:

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

4
1 để so sánh phương sai, nhưng không tính đến sai tuyệt đối (ví dụ như những gì nếu date1 là sau date2?)
thuộc về loài rắn

13
Tôi thường áp dụng cách tương tự chỉ gói rằng với Math.abs ()
parxier

59

Có các thư viện trợ giúp việc này:

Apache commons-lang

Nếu bạn có dấu phẩy-lang của Apache trên classpath của mình, bạn có thể sử dụng DateUtils.truncateđể cắt bớt ngày tháng cho một số trường.

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

Có một cách viết tắt cho điều này:

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

Lưu ý rằng 12: 00: 00.001 và 11: 59: 00.999 sẽ cắt ngắn thành các giá trị khác nhau, vì vậy điều này có thể không lý tưởng. Đối với điều đó, có một vòng:

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

AssertJ

Bắt đầu với phiên bản 3.7.0, AssertJ đã thêm một isCloseToxác nhận, nếu bạn đang sử dụng 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));

Nó cũng hoạt động với java Dates cũ:

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

đây là giải pháp tôi đang tìm kiếm :)
geoaxis

1
Cảm ơn điều này đã tiết kiệm cho tôi rất nhiều thời gian!
Robert Beltran

Tại sao không sử dụng DateUtils.round?
domi

1
Round cũng sẽ hoạt động. Nó sẽ làm tròn lên hoặc xuống, trong khi phần cắt ngắn sẽ luôn đi xuống. Theo tài liệu , vòng cũng xử lý thời gian tiết kiệm ánh sáng ban ngày.
Dan Watt

Tôi đã gặp vấn đề tương tự java.sql.Timestampsvà sự DateUtils.truncate(...)cố đã làm việc cho tôi trong Java 8. Trường hợp cụ thể của tôi bao gồm công nghệ cơ sở dữ liệu không hỗ trợ lưu bất kỳ hạt mịn nào hơn một giây, vì vậy tôi đang so sánh Dấu thời gian trong bộ nhớ với dấu thời gian đã được lưu và lấy từ cơ sở dữ liệu. Dấu thời gian trong bộ nhớ có độ chính xác cao hơn Dấu thời gian được đọc từ cơ sở dữ liệu.
Kent Bull

6

Bạn có thể làm điều gì đó như sau:

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

Không cần so sánh chuỗi.


Tôi nghĩ ý bạn là "/" so với "%"? Điều này trở nên lộn xộn liên quan đến độ chính xác tùy ý, IMHO. Tuy nhiên, điểm tốt.
Michael Easter

Rất tiếc! Nắm bắt tốt. Tôi không nghĩ rằng độ chính xác là một vấn đề. Date.getTime () luôn trả về một đoạn dài mili giây kể từ kỷ nguyên.
Seth

1
Điều này sẽ không thành công nếu một giá trị là 3,999 giây và giá trị khác là 4.000. Nói cách khác, đôi khi nó sẽ chịu được sự chênh lệch lên đến một giây, đôi khi nó sẽ không thành công với sự chênh lệch 2 ms.
David Balažic

6

Trong JUnit, bạn có thể lập trình hai phương thức xác nhận, như sau:

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

Tôi không biết có hỗ trợ trong JUnit không, nhưng có một cách để làm điều đó:

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

Nếu bạn gọi phương thức assertEqualDatesthì tôi sẽ tạo kiểu trả về của nó voidvà tạo dòng cuối cùng assertEquals(d1, d2). Bằng cách này, nó sẽ hoạt động giống như tất cả các assert*phương thức JUnit .
Joachim Sauer

Đã đồng ý. Tôi muốn chạy mã và không có JUnit trong tay.
Michael Easter

1
Hãy cảnh giác với các định dạng ngày toàn cầu. Chúng không an toàn theo luồng. Nó không phải là vấn đề với mã này, nhưng nó là một thói quen xấu.
itadok

1
Điều này không xử lý trường hợp hai đối tượng Ngày có sự khác biệt nhỏ hơn giây nhưng chúng vượt qua ngưỡng thứ hai.
Ophidian

3

Đây thực sự là một vấn đề khó hơn nó xuất hiện vì các trường hợp ranh giới trong đó phương sai mà bạn không quan tâm vượt qua ngưỡng cho một giá trị bạn đang kiểm tra. ví dụ: chênh lệch mili giây nhỏ hơn một giây nhưng hai dấu thời gian vượt qua ngưỡng thứ hai, hoặc ngưỡng phút, hoặc ngưỡng giờ. Điều này làm cho bất kỳ cách tiếp cận DateFormat nào vốn dễ bị lỗi.

Thay vào đó, tôi khuyên bạn nên so sánh các dấu thời gian mili giây thực tế và cung cấp một delta phương sai cho biết những gì bạn cho là sự khác biệt có thể chấp nhận được giữa hai đối tượng ngày. Một ví dụ quá dài dòng như sau:

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

Sử dụng JUnit 4, bạn cũng có thể triển khai một trình so khớp cho các ngày thử nghiệm theo độ chính xác đã chọn của bạn. Trong ví dụ này, trình so khớp lấy biểu thức định dạng chuỗi làm tham số. Mã không ngắn hơn cho ví dụ này. Tuy nhiên, lớp đối sánh có thể được sử dụng lại; và nếu bạn đặt cho nó một cái tên mô tả, bạn có thể ghi lại ý định bằng bài kiểm tra một cách trang nhã.

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

sử dụng xác nhận AssertJ cho 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()));

thông báo kiểm tra thất bại dễ đọc hơn

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 cũng làm việc cho java.util.date:assertThat(new Date(2016 - 1900, 0, 1,12,13,14)).isEqualToIgnoringMillis("2016-01-01T12:13:14");
Dan Watt

1

Nếu bạn đang sử dụng Joda, bạn có thể sử dụng Fest Joda Time .


3
bạn có thể cung cấp thêm thông tin về cách thực hiện điều này không? Nếu không, điều này sẽ được chuyển đổi thành một bình luận.
Hugo Dozois

1

Chỉ cần so sánh các phần ngày mà bạn muốn so sánh:

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

Tôi thích cách tiếp cận này hơn rất nhiều khi sử dụng công cụ định dạng ngày tháng. Chỉ có một vấn đề là các trường getter cụ thể trong Date không được dùng nữa. Tốt hơn nên sử dụng Lịch để làm điều tương tự.
kfox

À, điểm tốt cần lưu ý là những phương pháp đó không được dùng nữa. Thay vào đó, tôi đã cập nhật câu trả lời của mình bằng mã thay thế để chuyển đổi và so sánh các đối tượng Lịch.
Oliver Hernandez,

1

JUnit có một xác nhận tích hợp để so sánh các cặp đôi và chỉ định mức độ gần nhau của chúng. Trong trường hợp này, delta nằm trong khoảng bao nhiêu mili giây mà bạn coi là các ngày tương đương. Giải pháp này không có điều kiện biên, đo phương sai tuyệt đối, có thể dễ dàng chỉ định độ chính xác và không yêu cầu viết thêm thư viện hoặc mã.

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

Lưu ý Nó phải là 100.0 thay vì 100 (hoặc cần đúc thành nhân đôi) để buộc nó phải so sánh chúng dưới dạng nhân đôi.


1

Bạn có thể chọn mức độ chính xác bạn muốn khi so sánh các ngày, ví dụ:

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

Một cái gì đó như thế này có thể hoạt động:

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

0

Thay vì sử dụng new Datetrực tiếp, bạn có thể tạo một cộng tác viên nhỏ, mà bạn có thể mô phỏng trong bài kiểm tra của mình:

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

Tạo thành viên DateBuilder và thay đổi cuộc gọi từ new DatethànhdateBuilder.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();
    }
}

Phương pháp chính sẽ tạo ra:

Dates are the same: false

Trong thử nghiệm, bạn có thể chèn một đoạn sơ khai DateBuildervà để nó trả về bất kỳ giá trị nào bạn thích. Ví dụ với Mockito hoặc một lớp ẩn danh ghi đè 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

Chuyển đổi ngày tháng thành Chuỗi bằng cách sử dụng SimpleDateFromat, chỉ định trong hàm tạo các trường ngày / giờ bắt buộc và so sánh các giá trị chuỗi:

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


0

Đây là một chức năng tiện ích đã làm công việc cho tôi.

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


-1

tôi truyền các đối tượng sang java.util.Date và so sánh

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

Điều này sẽ khiến khẳng định bị lỗi vì độ chính xác.
TechCrunch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.