簡體   English   中英

將 java.time.Instant 轉換為沒有區域偏移量的 java.sql.Timestamp

[英]Convert java.time.Instant to java.sql.Timestamp without Zone offset

在我正在開發的應用程序中,我需要將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對象來做到這一點?

為了進行實驗,我將計算機的時區更改為 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 UTC。 所以這個方法是騙人的,看起來有效,但實際上並沒有。

這顯示了導致設計 Java 8 日期和時間類以替換舊的類的麻煩。 因此,解決您的問題的真正和好的解決方案可能是為自己准備一個 JDBC 4.2 驅動程序,它可以很容易地接受Instant對象,這樣您就可以避免完全轉換為Timestamp 我不知道這是否適合您,但我相信它會。

使用錯誤的類

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.DateCalendar ,和SimpleDateFormat

要了解更多信息,請參閱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 一起使用的表

如果您想要當前時間戳,為什么不使用以下功能,我已經在各種項目中使用了它並且運行良好:

public static Timestamp getTimeStamp()
{
    // Calendar information
    Calendar calendar       = Calendar.getInstance();
    java.util.Date now      = calendar.getTime();
    Timestamp dbStamp       = new Timestamp(now.getTime());
    return dbStamp;
}   

例子:

System.out.println( getTimeStamp() );

輸出: 2017-03-13 15:01:34.027

編輯

使用 Java 8 LocalDateTime:

public static Timestamp getTimeStamp()
{
    return Timestamp.valueOf(LocalDateTime.now());
}   

Instant 總是以 UTC 時間給出時間,而 Timestamp 給出本地時間。 因此,如果您不關心任何特定時區,則可以使用 Instant。 以 UTC 格式保存記錄也是一種很好的做法,這樣您的應用程序在部署在任何其他時區時不會受到影響。

在將記錄保存到 SQL Server DB 期間,我遇到了同樣的問題。 我使用java.sql.Timestamp.valueOf(String s)UTC 中獲取時間戳

import java.time.Instant; import java.time.format.DateTimeFormatter; .... .... DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(UTC); String dateTime = dateTimeFormatter.format(Instant date); Timestamp timestamp = Timestamp.valueOf(dateTime);

這個對我有用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM