【问题标题】:Compare Date objects with different levels of precision比较具有不同精度级别的 Date 对象
【发布时间】:2009-11-04 00:17:47
【问题描述】:

我有一个 JUnit 测试失败,因为毫秒不同。在这种情况下,我不关心毫秒。如何更改断言的精度以忽略毫秒(或我希望设置的任何精度)?

我想通过的失败断言示例:

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

【问题讨论】:

    标签: java junit


    【解决方案1】:

    另一种解决方法,我会这样做:

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

    【讨论】:

    • +1 用于比较方差,但不考虑绝对方差(例如,如果 date1 在 date2 之后怎么办?)
    • 我通常采用类似的方法,只是用 Math.abs() 包装它
    【解决方案2】:

    有一些库可以帮助解决这个问题:

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

    断言J

    从版本 3.7.0 开始,AssertJ 添加了一个isCloseTo 断言,如果您使用的是 Java 8 日期/时间 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());
    

    【讨论】:

    • 这是我一直在寻找的解决方案 :)
    • 感谢这为我节省了大量时间!
    • Round 也可以。它会向上或向下舍入,而截断总是向下。根据docs,round 还处理夏令时。
    • 我遇到了与java.sql.Timestamps 相同的问题,而DateUtils.truncate(...) 在 Java 8 中为我工作。我的特殊情况包括不支持保存任何比一秒更细粒度的数据库技术,所以我正在将内存中的时间戳与已从数据库中保存和检索的时间戳进行比较。内存中的时间戳比从数据库中读取的时间戳具有更高的精度。
    • assertJ 和 round() 方法是要走的路。好贴完整代码。
    【解决方案3】:

    使用DateFormat 对象,其格式仅显示您要匹配的部分,并对生成的字符串执行assertEquals()。您也可以轻松地将其包装在您自己的 assertDatesAlmostEqual() 方法中。

    【讨论】:

    • 不处理第二个边界上毫秒差异的情况,10.000 和 09.999 会有所不同。
    【解决方案4】:

    你可以这样做:

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

    不需要字符串比较。

    【讨论】:

    • 我认为您的意思是“/”与“%”?对于任意精度,恕我直言,这会变得混乱。不过好点。
    • 哎呀!接得好。我不认为精度是一个问题。 Date.getTime() 总是返回自纪元以来的长毫秒。
    • 如果一个值为 3.999 秒而另一个值为 4.000,这将失败。换句话说,有时它会容忍长达 1 秒的差异,有时它会因 2 毫秒的差异而失败。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      您可以在比较日期时选择所需的精度级别,例如:

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

      【讨论】:

        【解决方案7】:

        我不知道 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* 方法相同。
        • 同意。我想运行代码,但手头没有 JUnit。
        • 警惕全局日期格式化程序。它们不是线程安全的。这段代码没有问题,但这是一个坏习惯。
        • 这不处理两个 Date 对象具有亚秒级差异但跨越第二个阈值的情况。
        【解决方案8】:

        这实际上是一个比看起来更难的问题,因为在边界情况下,您不关心的方差会超过您正在检查的值的阈值。例如毫秒差小于一秒,但两个时间戳超过了第二个阈值、分钟阈值或小时阈值。这使得任何 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);
        }
        

        【讨论】:

          【解决方案9】:

          使用 AssertJ,您可以提供一个自定义比较器,如果您要比较整个对象结构而不是单个值,这会特别方便,因此 isEqualToIgnoringMillisisCloseTo 等其他方法不实用。

          assertThat(thing)
            .usingRecursiveComparison()
            .withComparatorForType(
                (a, b) -> a.truncatedTo(ChronoUnit.MILLIS).compareTo(b.truncatedTo(ChronoUnit.MILLIS)),
                OffsetDateTime.class
            )
          

          【讨论】:

          • 我还必须调整偏移量以使测试可靠。 (a, b) -&gt; a.truncatedTo(ChronoUnit.MILLIS).withOffsetSameInstant(ZoneOffset.UTC).compareTo(b.truncatedTo(ChronoUnit.MILLIS).withOffsetSameInstant(ZoneOffset.UTC)),
          【解决方案10】:

          使用 JUnit 4,您还可以实现 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());
                  }
              };
          }
          

          【讨论】:

            【解决方案11】:

            为 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.
            

            【讨论】:

            • AssertJ 也适用于 java.util.date:assertThat(new Date(2016 - 1900, 0, 1,12,13,14)).isEqualToIgnoringMillis("2016-01-01T12:13:14");
            【解决方案12】:

            如果您使用的是 Joda,则可以使用 Fest Joda Time

            【讨论】:

            • 您能否提供更多信息来说明如何实施?否则,应将其转换为评论。
            【解决方案13】:

            只需比较您有兴趣比较的日期部分:

            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 字段已被弃用。最好使用日历来做同样的事情。
            • 啊,请注意这些方法已被弃用。我已经用替代代码更新了我的答案,以转换和比较日历对象。
            【解决方案14】:

            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(或者需要强制转换为双精度)才能强制将它们作为双精度进行比较。

            【讨论】:

              【解决方案15】:

              这样的事情可能会奏效:

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

              【讨论】:

                【解决方案16】:

                您可以创建一个小型协作者,而不是直接使用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();
                    }
                }
                

                main方法会产生:

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

                【讨论】:

                  【解决方案17】:

                  使用 SimpleDateFromat 将日期转换为字符串,在构造函数中指定所需的日期/时间字段并比较字符串值:

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

                  【讨论】:

                    【解决方案18】:

                    我做了一个小班,可能对最终来到这里的一些谷歌员工有用:https://stackoverflow.com/a/37168645/5930242

                    【讨论】:

                      【解决方案19】:

                      这是一个为我完成这项工作的实用函数。

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

                      【讨论】:

                        【解决方案20】:

                        您可以使用isEqualToIgnoringSeconds 方法忽略秒并仅按分钟进行比较:

                        Date d1 = new Date();
                        Thread.sleep(10000);
                        Date d2 = new Date();
                        assertThat(d1).isEqualToIgnoringSeconds(d2); // true
                        

                        【讨论】:

                          【解决方案21】:

                          我将对象转换为 java.util.Date 并进行比较

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

                          【讨论】:

                          • 这会导致assert因为精度问题而失败。
                          猜你喜欢
                          • 1970-01-01
                          • 2015-09-12
                          • 2012-02-23
                          • 1970-01-01
                          • 2020-04-26
                          • 1970-01-01
                          • 1970-01-01
                          • 2017-08-06
                          • 1970-01-01
                          相关资源
                          最近更新 更多