简体   繁体   中英

java.sql.Timestamp made from java.time.Instant's MIN/MAX behaves differently than when constructed from Long.MIN_VALUE / Long.MAX_VALUE

I came across this issue while writing a test case where I had to get a range of records between a range of timestamps –– using H2 embedded database with spring-data-jpa .

The original issue is located at: Fetching records BETWEEN two java.time.Instant instances in Spring Data Query

I have the timestamps as java.time.Instant instances.

If the user gives no start-end timestamps, I go ahead and plug in Instant.MIN and Instant.MAX respectively.

What perplexes me is that the following test-case passes:

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

but, the following test-case fails:

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

Furthermore, if the past and future are not MIN/MAX values, but normal values instead, the result is as expected.

Any idea why java.sql.Timestamp behaves like this?

Also, if the time represented by Instant is too big for Timestamp shouldn't it fail instead?

PS If this question has already been asked, could someone link the original since I haven't been able to find it.

Edit: Added the debug information I mentioned in the comment section, so that we have everything in one place.

For the Timestamp instances made from Instant.MIN and Instant.MAX , I had the following values:

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

and for the Timestamp instances made from Long.MIN_VALUE and Long.MAX_VALUE , I got:

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

To clarify my question, instead of failing silently or using using a different value internally, the Timestamp should fail explicitly. Currently it doesn't.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM