2

我在编写一个测试用例时遇到了这个问题,我必须在一系列时间戳之间获取一系列记录——使用H2嵌入式数据库和spring-data-jpa.

原始问题位于:Fetching records BETWEEN two java.time.Instant instances in Spring Data Query

我有时间戳作为java.time.Instant实例。

如果用户没有给出开始结束时间戳,我会继续插入Instant.MINInstant.MAX

令我困惑的是,以下测试用例通过了:

  @Test
  public void test_date_min_max_instants_timestamps() {
    Timestamp past = new Timestamp(Long.MIN_VALUE); 
    Timestamp future = new Timestamp(Long.MAX_VALUE); 
    Timestamp present = Timestamp.from(Instant.now()); 

    assertTrue(present.after(past));
    assertTrue(future.after(past));
    assertTrue(future.after(present));

    assertTrue(present.before(future));
    assertTrue(past.before(present));
    assertTrue(past.before(future));
  }

但是,以下测试用例失败:

 @Test
  public void test_instant_ranges() throws InterruptedException {
    Timestamp past = Timestamp.from(Instant.MIN);
    Timestamp future = Timestamp.from(Instant.MAX);
    Timestamp present = Timestamp.from(Instant.now());

    assertTrue(present.after(past));
    assertTrue(future.after(past));
    assertTrue(future.after(present));

    assertTrue(present.before(future));
    assertTrue(past.before(present));
    assertTrue(past.before(future));
  }

此外,如果pastfuture不是 MIN/MAX 值,而是正常值,则结果符合预期。

知道为什么 java.sql.Timestamp 会这样吗?

另外,如果 Instant 表示的时间对于 Timestamp 来说太大了,它不应该失败吗?

PS如果这个问题已经被问过,有人可以链接原件,因为我找不到它。

编辑:添加了我在评论部分提到的调试信息,以便我们将所有内容集中在一个地方。

对于由andTimestamp制成的实例,我有以下值:Instant.MINInstant.MAX

past = 169108098-07-03 21:51:43.0 
future = 169104627-12-11 11:08:15.999999999 
present = 2018-07-23 10:46:50.842 

对于由andTimestamp制成的实例,我得到了:Long.MIN_VALUELong.MAX_VALUE

past = 292278994-08-16 23:12:55.192 
future = 292278994-08-16 23:12:55.807 
present = 2018-07-23 10:49:54.281

为了澄清我的问题,时间戳应该明确失败,而不是静默失败或在内部使用不同的值。目前没有。

4

1 回答 1

4

This is a known bug in the Timestamp class and its conversion from Instant. It was registered in the Java bug database in January 2015, three and a half years ago (and is still open with no decided fix version). See the link to the official bug report at the bottom.

Expected behaviour is clear

The documentation of Timestamp.from(Instant) is pretty clear about this:

Instant can store points on the time-line further in the future and further in the past than Date. In this scenario, this method will throw an exception.

So yes, an exception should be thrown.

It’s straightforward to reproduce the bug

On my Java 10 I have reproduced a couple of examples where the conversion silently gives an incorrect result rather than throwing an exception. One example is:

        Instant i = LocalDate.of(-400_000_000, Month.JUNE, 14)
                .atStartOfDay(ZoneId.of("Africa/Cairo"))
                .toInstant();
        Timestamp ts = Timestamp.from(i);
        System.out.println("" + i + " -> " + ts + " -> " + ts.toInstant());

This prints:

-400000000-06-13T21:54:51Z -> 184554049-09-14 14:20:42.0 -> +184554049-09-14T12:20:42Z

The former conversion is very obviously wrong: a time in the far past has been converted into a time in the far (though not quite as far) future (the conversion back to Instant seems to be correct).

Appendix: JDK source code

For the curious here is the implementation of the conversion method:

public static Timestamp from(Instant instant) {
    try {
        Timestamp stamp = new Timestamp(instant.getEpochSecond() * MILLIS_PER_SECOND);
        stamp.nanos = instant.getNano();
        return stamp;
    } catch (ArithmeticException ex) {
        throw new IllegalArgumentException(ex);
    }
}

It may seem that the author had expected that an arithmetic overflow in the multiplication would cause an ArithmeticException. It does not.

Links

于 2018-07-25T08:21:17.563 回答