java.sql.Timestamp 由 java.time.Instant 的 MIN/MAX 构建的行为不同于由 Long.MIN_VALUE / Long.MAX_VALUE 构建的行为
java.sql.Timestamp made from java.time.Instant's MIN/MAX behaves differently than when constructed from Long.MIN_VALUE / Long.MAX_VALUE
我在编写测试用例时遇到了这个问题,我必须在一系列时间戳之间获取一系列记录——使用 H2
嵌入式数据库和 spring-data-jpa
。
原刊位于:
我有 java.time.Instant
个实例的时间戳。
如果用户没有提供起止时间戳,我继续并分别插入Instant.MIN
和Instant.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));
}
此外,如果past
和future
不是MIN/MAX值,而是正常值,则结果符合预期。
知道为什么 java.sql.Timestamp 会这样吗?
此外,如果 Instant 表示的时间对于 Timestamp 来说太大了,它不应该失败吗?
P.S。如果这个问题已经被问过,有人可以 link 原来的,因为我找不到它。
编辑:添加了我在评论部分提到的调试信息,以便我们将所有内容集中在一个地方。
对于由 Instant.MIN
和 Instant.MAX
生成的 Timestamp
个实例,我有以下值:
past = 169108098-07-03 21:51:43.0
future = 169104627-12-11 11:08:15.999999999
present = 2018-07-23 10:46:50.842
并且对于由 Long.MIN_VALUE
和 Long.MAX_VALUE
生成的 Timestamp
个实例,我得到:
past = 292278994-08-16 23:12:55.192
future = 292278994-08-16 23:12:55.807
present = 2018-07-23 10:49:54.281
为了澄清我的问题,时间戳应该显式失败,而不是默默地失败或在内部使用不同的值。目前没有。
这是 Timestamp
class 及其从 Instant
转换而来的已知错误。它于三年半前的 2015 年 1 月在 Java 错误数据库中注册(并且仍然开放,没有确定的修复版本)。请参阅底部的 link 到官方错误报告。
预期的行为很明确
Timestamp.from(Instant)
的文档对此非常清楚:
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.
所以是的,应该抛出异常。
重现bug很简单
在我的 Java 10 上,我重现了几个示例,在这些示例中,转换默默地给出了不正确的结果,而不是抛出异常。一个例子是:
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());
这会打印:
-400000000-06-13T21:54:51Z -> 184554049-09-14 14:20:42.0 -> +184554049-09-14T12:20:42Z
前一个转换显然是错误的:很久以前的时间被转换成遥远(虽然不是很远)的未来(转换回 Instant
似乎是正确的).
附录:JDK源代码
好奇这里是转换方法的实现:
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);
}
}
似乎作者预料到乘法运算溢出会导致ArithmeticException
。它没有。
链接
我在编写测试用例时遇到了这个问题,我必须在一系列时间戳之间获取一系列记录——使用 H2
嵌入式数据库和 spring-data-jpa
。
原刊位于:
我有 java.time.Instant
个实例的时间戳。
如果用户没有提供起止时间戳,我继续并分别插入Instant.MIN
和Instant.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));
}
此外,如果past
和future
不是MIN/MAX值,而是正常值,则结果符合预期。
知道为什么 java.sql.Timestamp 会这样吗?
此外,如果 Instant 表示的时间对于 Timestamp 来说太大了,它不应该失败吗?
P.S。如果这个问题已经被问过,有人可以 link 原来的,因为我找不到它。
编辑:添加了我在评论部分提到的调试信息,以便我们将所有内容集中在一个地方。
对于由 Instant.MIN
和 Instant.MAX
生成的 Timestamp
个实例,我有以下值:
past = 169108098-07-03 21:51:43.0
future = 169104627-12-11 11:08:15.999999999
present = 2018-07-23 10:46:50.842
并且对于由 Long.MIN_VALUE
和 Long.MAX_VALUE
生成的 Timestamp
个实例,我得到:
past = 292278994-08-16 23:12:55.192
future = 292278994-08-16 23:12:55.807
present = 2018-07-23 10:49:54.281
为了澄清我的问题,时间戳应该显式失败,而不是默默地失败或在内部使用不同的值。目前没有。
这是 Timestamp
class 及其从 Instant
转换而来的已知错误。它于三年半前的 2015 年 1 月在 Java 错误数据库中注册(并且仍然开放,没有确定的修复版本)。请参阅底部的 link 到官方错误报告。
预期的行为很明确
Timestamp.from(Instant)
的文档对此非常清楚:
Instant
can store points on the time-line further in the future and further in the past thanDate
. In this scenario, this method will throw an exception.
所以是的,应该抛出异常。
重现bug很简单
在我的 Java 10 上,我重现了几个示例,在这些示例中,转换默默地给出了不正确的结果,而不是抛出异常。一个例子是:
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());
这会打印:
-400000000-06-13T21:54:51Z -> 184554049-09-14 14:20:42.0 -> +184554049-09-14T12:20:42Z
前一个转换显然是错误的:很久以前的时间被转换成遥远(虽然不是很远)的未来(转换回 Instant
似乎是正确的).
附录:JDK源代码
好奇这里是转换方法的实现:
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);
}
}
似乎作者预料到乘法运算溢出会导致ArithmeticException
。它没有。