Java - 将java.time.Instant转换为没有Zone偏移量的java.sql.Timestamp

Ale*_*min 32 java time timestamp date java.time.instant

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

Instant now = Instant.now();
Run Code Online (Sandbox Code Playgroud)

并检查元素.我收到类似的东西2017-03-13T14:28:59.970Z 然后我尝试创建这样的Timestamp对象:

Timestamp current = Timestamp.from(now);
Run Code Online (Sandbox Code Playgroud)

当我检查元素时.我收到类似2017-03-13T16:28:59.970Z 相同的结果,但增加了2小时的偏移量.任何人都可以解释你的这种情况,并为我提供答案如何解决这个问题而不会产生抵消?

当我这样创建时:

LocalDateTime ldt = LocalDateTime.ofInstant(Instnant.now(), ZoneOffset.UTC);
Timestamp current = Timestamp.valueOf(ldt);
Run Code Online (Sandbox Code Playgroud)

一切正常.但我尽量避免转换.有没有办法只使用Instant对象?

Ole*_*.V. 40

我将计算机的时区更改为欧洲/布加勒斯特进行实验.这是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
Run Code Online (Sandbox Code Playgroud)

输出在评论中给出.但是,我继续说:

    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));
Run Code Online (Sandbox Code Playgroud)

现在你可以看到我们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
Run Code Online (Sandbox Code Playgroud)

现在,当我们打印Timestamp使用我们的UTC时DateFormat,我们可以看到太早2小时,04:16:32 UTC时间Instant是06:16:32 UTC.所以这种方法是欺骗性的,看起来它在起作用,但事实并非如此.

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


Bas*_*que 17

使用错误的类

LocalDateTime ldt = LocalDateTime.ofInstant(Instnant.now(), ZoneOffset.UTC);
Timestamp current = Timestamp.valueOf(ldt);
Run Code Online (Sandbox Code Playgroud)

该代码存在两个问题.

首先,永远不要将现代java.time类(LocalDateTime这里)与可怕的旧遗留日期时间类(java.sql.Timestamp这里)混合在一起.该java.time框架完全取代了旧的可怕类,采用的JSR 310.您永远不需要Timestamp再次使用:从JDBC 4.2开始,我们可以直接与数据库交换java.time对象.

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

千万不能使用 LocalDateTime时,你的意思是一个特定的时刻,在时间轴上的特定点.而是使用:

然后我尝试创建Timestamp对象

别.

切勿使用java.sql.Timestamp.替换为java.time.Instant.有关详细信息,请参见下文

当下时刻

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

Instant instant = Instant.now() ;
Run Code Online (Sandbox Code Playgroud)

…要么…

OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC );
Run Code Online (Sandbox Code Playgroud)

这两者代表了同样的事情,是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();
}
Run Code Online (Sandbox Code Playgroud)

这是一个使用该代码的完整示例应用程序.

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

解析字符串

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

输入字符串缺少任何时区指示符或与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 );
Run Code Online (Sandbox Code Playgroud)

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

但是我们已经被告知字符串意味着代表UTC的一个时刻,但是发送者搞砸了并且未能包含该信息(例如a Z或者+00:00结尾意味着UTC).因此,我们可以应用零小时 - 分钟 - 秒的UTC偏移量来确定实际时刻,即时间线上的特定点.结果作为OffsetDateTime对象.

OffsetDateTime odt = ldt.atOffset( ZoneOffset.UTC );
Run Code Online (Sandbox Code Playgroud)

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

Z上月底表示UTC发音为"祖鲁".定义于ISO 8601标准.

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

preparedStatement.setObject( 2 , odt );
Run Code Online (Sandbox Code Playgroud)

然后检索该存储的值.

 OffsetDateTime odt = rs.getObject( "when_" , OffsetDateTime.class );
Run Code Online (Sandbox Code Playgroud)

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

关于java.time

java.time框架是建立在Java 8和更高版本.这些类取代麻烦的老传统日期时间类,如to…,from…,和java.sql.Timestamp.

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

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

您可以直接与数据库交换java.time对象.使用符合JDBC 4.2或更高版本的JDBC驱动程序.不需要字符串,不需要课程.Timestamp.from( Instant )

从哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time.该项目是未来可能添加到java.time的试验场.您可以在此比如找到一些有用的类Instant,OffsetDateTime,OffsetDateTime::toInstant,和更多.

  • @ Dogbert_1825027(A)关于需要与尚未更新为* java.time *的旧代码进行互操作的需要,很好。您的评论促使我在底部附近添加了* Converting *部分,并在旧有类与现代类之间来回移动了示例代码。(B)至于我坚持避免使用遗留类,我坚持我的立场。传统的日期时间类确实很糟糕,在许多方面都存在缺陷,使用起来非常困难,这可以从Stack Overflow上大量的问题中看出。将* ThreeTen-Backport *添加到旧的Java 6/7项目中非常值得。 (2认同)