使用 H2 数据库 JDBC 中的年份从负 -509 变为正 510

Year changing from negative -509 to a positive 510 in JDBC with H2 Database

-509 对比 510

我发现使用 JDBC 时出现了某种更改或错误的数据。所以我观察到在 Java 8 更新 151.

上使用 H2 Database 版本 1.4.196

这是一个完整的例子。

注意我们如何检索日期值三次,第一次作为 LocalDate 对象,第二次作为文本,第三次作为从强制转换 LocalDate 对象中提取的 int 年份数字.在文字版中我们可以看到年份确实是负数。神秘的是 LocalDate 有一个不同的年份数字,它是正数而不是负数。似乎是一个错误。

private void doIt ( )
{
    System.out.println( "BASIL - Running doIt." );
    try
    {
        Class.forName( "org.h2.Driver" );
    } catch ( ClassNotFoundException e )
    {
        e.printStackTrace( );
    }

    try (
            Connection conn = DriverManager.getConnection( "jdbc:h2:mem:" ) ;  // Unnamed throw-away in-memory database.
    )
    {
        conn.setAutoCommit( true );
        String sqlCreate = "CREATE TABLE history  ( id IDENTITY , when  DATE ); ";
        String sqlInsert = "INSERT INTO history ( when ) VALUES ( ? ) ; ";
        String sqlQueryAll = "SELECT * FROM history  ; ";

        PreparedStatement psCreate = conn.prepareStatement( sqlCreate );

        psCreate.executeUpdate( );

        PreparedStatement psInsert = conn.prepareStatement( sqlInsert );

        psInsert.setObject( 1 , LocalDate.of( 2017 , 1 , 23 ) );
        psInsert.executeUpdate( );

        psInsert.setObject( 1 , LocalDate.of( -509 , 1 , 1 ) );
        psInsert.executeUpdate( );

        PreparedStatement psQueryAll = conn.prepareStatement( sqlQueryAll );
        ResultSet rs = psQueryAll.executeQuery( );
        while ( rs.next( ) )
        {
            long l = rs.getLong( 1 );  // Identity column.
            // Retrieve the same date value in three different ways.
            LocalDate ld = rs.getObject( 2 , LocalDate.class );  // Extract a `LocalDate`, and implicitly call its `toString` method that uses standard ISO 8601 formatting.
            String s = rs.getString( 2 );  // Extract the date value as text from the database using the database-engine’s own formatting.
            int y = ( ( LocalDate ) rs.getObject( 2 , LocalDate.class ) ).getYear( );  // Extract the year number as an integer from a `LocalDate` object.
            String output = "ROW: " + l+ " | " + ld + " | when as String: " + s+ " | year: " + y ;
            System.out.println( output );
        }

        conn.close( );
    } catch ( SQLException e )
    {
        e.printStackTrace( );
    }
}

当运行.

ROW: 1 | 2017-01-23 | when as String: 2017-01-23 | year: 2017

ROW: 2 | 0510-01-01 | when as String: -509-01-01 | year: 510

因此,JDBC 似乎正在发生一些事情。请注意年份是如何显示为正 510 而不是负 509。我不理解这种行为。

我可以推断这是 JDBC rather than within LocalDate. See this example code run live in IdeOne.com 中的一个问题,表明 LocalDate 对象确实携带并报告负年份。

LocalDate ld = LocalDate.of( -509 , 1 , 1 ) ;
System.out.println( "ld.toString(): " + ld ) ;
System.out.println( "ld.getYear(): " + ld.getYear() ) ;

注意我们如何做 not 在只处理 LocalDate 而没有 JDBC.[=27= 时从 -509 转换为 510 ]

ld: -0509-01-01

ld.getYear(): -509

我在H2项目上开了一个Issue ticket

问题是由java.sql.Date转换成LocalDate引起的 . 因为它是负数年份,所以 Calendar 实例保存获取的结果会将 year 转换为 1 - year 但是当转换为 LocalDate java 没有考虑附加信息 (era==BC) 表明 year < 0 以下是返回之前执行的最终方法结果。

试试这个:

public class Test {
 public static void main(String[] args) {

            Calendar instance = Calendar.getInstance();
            instance.set(-509,1,1);

            java.sql.Date d = new Date(instance.getTime().getTime());

            System.out.println(d.toLocalDate().getYear());// 510


        }
}

谢谢奥莱 V.V。征求意见!!!

TL;DR:如果 JDBC 驱动程序内部使用 java.sql.Date 并使用 java.sql.Date.toLocalDate() 转换它,则可能是已弃用 Date.getYear() 将(至少有时)导致您观察到的行为。

这是猜测,但我发现它很有趣,可以分享。

我从 了解到,驱动程序确实使用了一个或多个遗留日期和时间 类,至少 CalendarGregorianCalendar。从 CalendarLocalDate 的转换路径更多,因此通过 java.sql.Date 的转换路径只是其中之一。不过,其他转化路径可能会遇到同样的错误。

事实:toLocalDate 方法依赖于已弃用的 getYear 方法。资料来源:

@SuppressWarnings("deprecation")
public LocalDate toLocalDate() {
    return LocalDate.of(getYear() + 1900, getMonth() + 1, getDate());
}

要查看 getYear 在公历前一年的表现,我尝试了:

    OffsetDateTime dateTimeBce 
            = OffsetDateTime.of(-509, 1, 1, 0, 0, 0, 0, ZoneOffset.ofHours(1));
    Date d = Date.from(dateTimeBce.toInstant());
    System.out.println("d.toInstant() " + d.toInstant());
    System.out.println("d.getYear() (deprecated): " + d.getYear() 
                        + ", means " + (d.getYear() + 1900));

由于 getYear 的年份是“基于 1900 年”,因此预期年份为 -2409。如果我们将 1900 添加到此,我们将得到 -509,即我们开始的年份。然而,该片段打印:

d.toInstant() -0510-12-31T23:00:00Z
d.getYear() (deprecated): -1390, means 510

第一行显示 Date 确实包含负年份(UTC 的偏移量转换将年份从 -509 更改为 -510;我选择了计算机时间的标准时间偏移量区域设置)。该片段使用 java.util.Date,但 java.sql.Date 继承了 getYear 方法,我也用 java.sql.Date 重现了类似的行为。

我在 Internet 上进行了简短的搜索,没有找到任何关于可疑错误的提及。我们可能想更加努力。

错误,已修复

此问题是由 bug in H2 引起的。

现已修复,截至 2018-01。