将 java.time.Instant 转换为没有区域偏移的 java.sql.Timestamp

新手上路,请多包涵

在我正在开发的应用程序中,我需要将 java.time.Instant 对象转换为 java.sql.Timestamp 。当我创建 Instant 对象时:

 Instant now = Instant.now();

我收到类似 2017-03-13T14:28:59.970Z 的信息。当我尝试创建 Timestamp 对象时,如下所示:

 Timestamp current = Timestamp.from(now);

我收到类似 2017-03-13T16:28:59.970Z 的信息。结果相同,但延迟了 2 小时。有人可以解释为什么会发生这种情况并为我提供解决此问题的答案而不会拖延吗?

当我这样创建时:

 LocalDateTime ldt = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC);
Timestamp current = Timestamp.valueOf(ldt);

一切正常,但我尽量避免转换。有没有办法只使用 Instant 对象来做到这一点?

原文由 Aleydin Karaimin 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 560
2 个回答

我将计算机的时区更改为 Europe/Bucharest 进行实验。这是 UTC + 2 小时,就像您的时区一样。

现在,当我复制您的代码时,我得到的结果与您的类似:

     Instant now = Instant.now();
    System.out.println(now); // prints 2017-03-14T06:16:32.621Z
    Timestamp current = Timestamp.from(now);
    System.out.println(current); // 2017-03-14 08:16:32.621

输出在注释中给出。但是,我继续:

     DateFormat df = DateFormat.getDateTimeInstance();
    df.setTimeZone(TimeZone.getTimeZone("UTC"));
    // the following prints: Timestamp in UTC: 14-03-2017 06:16:32
    System.out.println("Timestamp in UTC: " + df.format(current));

现在您可以看到 Timestamp 真的与我们开始的 Instant --- 一致(只有毫秒未打印,但我相信它们也在那里)。所以你已经正确地完成了所有事情,只是感到困惑,因为当我们打印 Timestamp 时,我们隐式调用了它的 toString 方法,而这个方法反过来获取计算机的时区设置并显示在这个区域的时间。仅此而已,显示有所不同。

你尝试的另一件事,使用 LocalDateTime ,似乎有效,但它确实没有给你你想要的东西:

     LocalDateTime ldt = LocalDateTime.ofInstant(Instant.now(), ZoneOffset.UTC);
    System.out.println(ldt); // 2017-03-14T06:16:32.819
    current = Timestamp.valueOf(ldt);
    System.out.println(current); // 2017-03-14 06:16:32.819
    System.out.println("Timestamp in UTC: " + df.format(current)); // 14-03-2017 04:16:32

现在,当我们使用我们的 UTC DateFormat 打印 Timestamp 时,我们可以看到它早了 2 小时,04:16:32 UTC 当 Instant 世界标准时间 06:16:32。所以这个方法是骗人的,貌似有效,其实不然。

这显示了导致设计 Java 8 日期和时间类以替换旧类的麻烦。因此,解决您的问题的真正和好的解决方案可能是让您自己拥有一个 JDBC 4.2 驱动程序,它可以轻松接受 Instant 对象,这样您就可以避免转换为 Timestamp 。我不知道你现在是否可以使用它,但我相信它会的。

原文由 Ole V.V. 发布,翻译遵循 CC BY-SA 3.0 许可协议

使用错误的类

LocalDateTime ldt = LocalDateTime.ofInstant(Instnant.now(), ZoneOffset.UTC);
Timestamp current = Timestamp.valueOf(ldt);

该代码有两个问题。

首先,切勿将现代 java.time 类(此处为 LocalDateTime )与可怕的旧旧日期时间类(此处为 java.sql.Timestamp )混合使用。随着 JSR 310 的采用, java.time 框架完全取代了可怕的旧类。您永远不需要再次使用 Timestamp :从 JDBC 4.2 开始,我们可以直接与数据库交换 java.time 对象。

另一个问题是 LocalDateTime 类不能根据定义代表一个时刻。它故意缺少时区或与 UTC 的偏移量。使用 LocalDateTime 仅当您指的是在任何 地方任何地方 都有时间的日期时,换句话说,大约 26-27 小时范围内的任何/所有更多时刻(当前时间的极端值)全球各地的区域)。

不要 使用 LocalDateTime 当你指的是特定时刻,时间轴上的特定点时。而是使用:

然后我尝试创建 Timestamp 对象

不。

切勿使用 java.sql.Timestamp 。替换为 java.time.Instant 。继续阅读以获取更多信息。

Java(现代和传统)和标准 SQL 中的日期时间类型表。

当前时刻

要捕获 UTC 中的当前时刻,请使用以下任一方法:

 Instant instant = Instant.now() ;

…或者…

 OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );

两者代表同一件事,UTC 中的一个时刻。

数据库

下面是一些示例 SQL 和 Java 代码,用于将当前时刻传递到数据库中。

该示例使用以 Java 构建的 H2 数据库引擎

 sql = "INSERT INTO event_ ( name_ , when_ ) " + "VALUES ( ? , ? ) ;";
try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; ) {
    String name = "whatever";
    OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );

    preparedStatement.setString( 1 , name );
    preparedStatement.setObject( 2 , odt );
    preparedStatement.executeUpdate();
}

这是使用该代码的完整示例应用程序。

 package com.basilbourque.example;

import java.sql.*;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.UUID;

public class MomentIntoDatabase {

    public static void main ( String[] args ) {
        MomentIntoDatabase app = new MomentIntoDatabase();
        app.doIt();
    }

    private void doIt ( ) {
        try {
            Class.forName( "org.h2.Driver" );
        } catch ( ClassNotFoundException e ) {
            e.printStackTrace();
        }

        try (
                Connection conn = DriverManager.getConnection( "jdbc:h2:mem:moment_into_db_example_" ) ;
                Statement stmt = conn.createStatement() ;
        ) {
            String sql = "CREATE TABLE event_ (\n" +
                    "  id_ UUID DEFAULT random_uuid() PRIMARY KEY ,\n" +
                    "  name_ VARCHAR NOT NULL ,\n" +
                    "  when_ TIMESTAMP WITH TIME ZONE NOT NULL\n" +
                    ") ; ";
            System.out.println( sql );
            stmt.execute( sql );

            // Insert row.
            sql = "INSERT INTO event_ ( name_ , when_ ) " + "VALUES ( ? , ? ) ;";
            try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; ) {
                String name = "whatever";
                OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );

                preparedStatement.setString( 1 , name );
                preparedStatement.setObject( 2 , odt );
                preparedStatement.executeUpdate();
            }

            // Query all.
            sql = "SELECT * FROM event_ ;";
            try ( ResultSet rs = stmt.executeQuery( sql ) ; ) {
                while ( rs.next() ) {
                    //Retrieve by column name
                    UUID id = ( UUID ) rs.getObject( "id_" );  // Cast the `Object` object to UUID if your driver does not support JDBC 4.2 and its ability to pass the expected return type for type-safety.
                    String name = rs.getString( "name_" );
                    OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );

                    //Display values
                    System.out.println( "id: " + id + " | name: " + name + " | when: " + odt );
                }
            }
        } catch ( SQLException e ) {
            e.printStackTrace();
        }
    }
}

解析字符串

关于 Melnyk 的相关评论,这是基于上述示例代码的另一个示例。此代码不是捕获当前时刻,而是解析字符串。

输入字符串缺少任何 时区 指示符或 offset-from-UTC 。所以我们解析为 LocalDateTime ,请记住这 并不 代表一个时刻, 也不 是时间轴上的一个点。

 String input = "22.11.2018 00:00:00";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd.MM.uuuu HH:mm:ss" );
LocalDateTime ldt = LocalDateTime.parse( input , f );

ldt.toString(): 2018-11-22T00:00

但是我们被告知该字符串是为了代表 UTC 中的一个时刻,但是发件人搞砸了并且未能包含该信息(例如 Z+00:00 最后平均 UTC)。因此,我们可以应用零小时-分钟-秒的 UTC 偏移量来确定实际时刻,即时间轴上的特定点。结果为 OffsetDateTime 对象。

 OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC );

odt.toString(): 2018-11-22T00:00Z

末尾的 Z 表示 UTC,发音为“Zulu”。在 ISO 8601 标准中定义。

现在我们有时间了,我们可以将它发送到 SQL 标准类型的列中的数据库 TIMESTAMP WITH TIME ZONE

 preparedStatement.setObject( 2 , odt );

然后检索该存储的值。

  OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );

2018-11-22T00:00Z

这是此示例应用程序的完整内容。

 package com.basilbourque.example;

import java.sql.*;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.UUID;

public class MomentIntoDatabase {

    public static void main ( String[] args ) {
        MomentIntoDatabase app = new MomentIntoDatabase();
        app.doIt();
    }

    private void doIt ( ) {
        try {
            Class.forName( "org.h2.Driver" );
        } catch ( ClassNotFoundException e ) {
            e.printStackTrace();
        }

        try (
                Connection conn = DriverManager.getConnection( "jdbc:h2:mem:moment_into_db_example_" ) ;
                Statement stmt = conn.createStatement() ;
        ) {
            String sql = "CREATE TABLE event_ (\n" +
                    "  id_ UUID DEFAULT random_uuid() PRIMARY KEY ,\n" +
                    "  name_ VARCHAR NOT NULL ,\n" +
                    "  when_ TIMESTAMP WITH TIME ZONE NOT NULL\n" +
                    ") ; ";
            System.out.println( sql );
            stmt.execute( sql );

            // Insert row.
            sql = "INSERT INTO event_ ( name_ , when_ ) " + "VALUES ( ? , ? ) ;";
            try ( PreparedStatement preparedStatement = conn.prepareStatement( sql ) ; ) {
                String name = "whatever";
                String input = "22.11.2018 00:00:00";
                DateTimeFormatter f = DateTimeFormatter.ofPattern( "dd.MM.uuuu HH:mm:ss" );
                LocalDateTime ldt = LocalDateTime.parse( input , f );
                System.out.println( "ldt.toString(): " + ldt );
                OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC );
                System.out.println( "odt.toString(): " + odt );

                preparedStatement.setString( 1 , name );
                preparedStatement.setObject( 2 , odt );
                preparedStatement.executeUpdate();
            }

            // Query all.
            sql = "SELECT * FROM event_ ;";
            try ( ResultSet rs = stmt.executeQuery( sql ) ; ) {
                while ( rs.next() ) {
                    //Retrieve by column name
                    UUID id = ( UUID ) rs.getObject( "id_" );  // Cast the `Object` object to UUID if your driver does not support JDBC 4.2 and its ability to pass the expected return type for type-safety.
                    String name = rs.getString( "name_" );
                    OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );

                    //Display values
                    System.out.println( "id: " + id + " | name: " + name + " | when: " + odt );
                }
            }
        } catch ( SQLException e ) {
            e.printStackTrace();
        }
    }
}

转换

如果您必须与尚未为 java.time 更新的旧代码进行互操作,您可以来回转换。查看新方法 to… / from… 添加到旧类中。

要获得遗留 java.sql.Timestamp 对象,请调用 Timestamp.from( Instant ) 。要从上面看到的 OffsetDateTime 对象中获取 Instant --- ,只需调用 OffsetDateTime::toInstant

 java.sql.Timestamp ts = Timestamp.from( odt.toInstant() ) ;

往另一个方向走。

 OffsetDateTime odt = OffsetDateTime.ofInstant( ts.toInstant() , ZoneOffset.UTC ) ;

如果将 ThreeTen-Backport 库用于 Java 6 和 7 项目,请查看 DateTimeUtils to… / from… 转换方法类


关于 java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

要了解更多信息,请参阅 Oracle 教程。并在 Stack Overflow 中搜索许多示例和解释。规范是 JSR 310

现在处于 维护模式Joda-Time 项目建议迁移到 java.time 类。

您可以直接与数据库交换 java.time 对象。使用符合 JDBC 4.2 或更高版本的 JDBC 驱动程序。不需要字符串,不需要 java.sql.* 类。 Hibernate 5 & JPA 2.2 支持 java.time

在哪里获取 java.time 类?

哪个 java.time 库与哪个版本的 Java 或 Android 一起使用的表

原文由 Basil Bourque 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题