简体   繁体   English

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

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

In the application I am developing, I need to convert java.time.Instant object to java.sql.Timestamp .在我正在开发的应用程序中,我需要将java.time.Instant对象转换为java.sql.Timestamp When I create Instant object like:当我创建Instant对象时:

Instant now = Instant.now();

I receive something like 2017-03-13T14:28:59.970Z .我收到类似2017-03-13T14:28:59.970Z And when I try to create Timestamp object like this:当我尝试像这样创建Timestamp对象时:

Timestamp current = Timestamp.from(now);

I receive something like 2017-03-13T16:28:59.970Z .我收到类似2017-03-13T16:28:59.970Z The same result but with an additional 2 hour delay.相同的结果,但额外延迟了 2 小时。 Can someone explain why this is happening and provide me with an answer to fix this problem without this delay?有人可以解释为什么会发生这种情况并为我提供解决此问题的答案,而不会出现这种延迟吗?

When I created like this:当我这样创建时:

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

Everything works well, but I try to avoid conversions.一切正常,但我尽量避免转换。 Is there a way to do this by only using Instant object?有没有办法只使用Instant对象来做到这一点?

I changed my computer's time zone to Europe/Bucharest for an experiment.为了进行实验,我将计算机的时区更改为 Europe/Bucharest。 This is UTC + 2 hours like your time zone.这是 UTC + 2 小时,就像您的时区一样。

Now when I copy your code I get a result similar to yours:现在,当我复制您的代码时,得到与您类似的结果:

    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

Output is given in comments.输出在注释中给出。 However, I go on:但是,我继续说:

    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));

Now you can see that the Timestamp really agrees with the Instant we started out from (only the milliseconds are not printed, but I trust they are in there too).现在您可以看到Timestamp确实与我们开始的Instant一致(只有毫秒没有打印,但我相信它们也在那里)。 So you have done everything correctly and only got confused because when we printed the Timestamp we were implicitly calling its toString method, and this method in turn grabs the computer's time zone setting and displays the time in this zone.所以你已经正确地完成了所有事情,只是感到困惑,因为当我们打印Timestamp我们隐式调用了它的toString方法,该方法反过来获取计算机的时区设置并显示该时区中的时间。 Only because of this, the displays are different.正因为如此,显示方式不同。

The other thing you attempted, using LocalDateTime , appears to work, but it really does not give you what you want:您尝试的另一件事,使用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

Now when we print the Timestamp using our UTC DateFormat , we can see that it is 2 hours too early, 04:16:32 UTC when the Instant is 06:16:32 UTC.现在,当我们使用 UTC DateFormat打印Timestamp时,我们可以看到它早了 2 小时,即 04:16:32 UTC,而Instant是 06:16:32 UTC。 So this method is deceiving, it looks like it's working, but it doesn't.所以这个方法是骗人的,看起来有效,但实际上并没有。

This shows the trouble that lead to the design of the Java 8 date and time classes to replace the old ones.这显示了导致设计 Java 8 日期和时间类以替换旧的类的麻烦。 So the real and good solution to your problem would probably be to get yourself a JDBC 4.2 driver that can accept an Instant object readily so you can avoid converting to Timestamp altogether.因此,解决您的问题的真正和好的解决方案可能是为自己准备一个 JDBC 4.2 驱动程序,它可以很容易地接受Instant对象,这样您就可以避免完全转换为Timestamp I don't know if that's available for you just yet, but I'm convinced it will be.我不知道这是否适合您,但我相信它会。

Wrong classes to use使用错误的类

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

Two problems with that code.该代码有两个问题。

Firstly, never mix the modern java.time classes ( LocalDateTime here) with the terrible old legacy date-time classes ( java.sql.Timestamp here).首先,永远不要将现代java.time类(此处为LocalDateTime )与糟糕的旧式遗留日期时间类(此处为java.sql.Timestamp )混合在一起。 The java.time framework supplants entirely the terrible old classes, as of the adoption of JSR 310 .从采用JSR 310 开始java.time框架完全取代了可怕的旧类。 You need never use Timestamp again: As of JDBC 4.2 we can directly exchange java.time objects with the database.您再也不需要使用Timestamp了:从JDBC 4.2 开始,我们可以直接与数据库交换java.time对象。

The other problem, is that the LocalDateTime class cannot, by definition, represent a moment.另一个问题是,根据定义, LocalDateTime类不能代表片刻。 It purposely lacks a time zone or offset-from-UTC.它故意缺少时区或 UTC 偏移量。 Use LocalDateTime only when you mean a date with time-of-day everywhere or anywhere , in other words, any/all of many more moments across a range of about 26-27 hours (the current extremes of time zones around the globe).仅当您的意思是任何地方任何地方都有时间的日期时才使用LocalDateTime ,换句话说,在大约 26-27 小时范围内的任何/所有更多时刻(当前全球时区的极端情况)。

Do not use LocalDateTime when you mean a specific moment, a specific point on the timeline.不要使用LocalDateTime当你的意思是一个特定的时刻,在时间轴上的特定点。 Instead use:而是使用:

Then I try to create Timestamp object然后我尝试创建 Timestamp 对象

Don't.别。

Never use java.sql.Timestamp .永远不要使用java.sql.Timestamp Replaced by java.time.Instant .替换为java.time.Instant Read on for more info.继续阅读以获取更多信息。

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

Current moment当前时刻

To capture the current moment in UTC use either of these:要捕获 UTC 中的当前时刻,请使用以下任一方法:

Instant instant = Instant.now() ;

…or… …或者…

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

The both represent the very same thing, a moment in UTC.两者都代表相同的事物,UTC 时间。

Database数据库

Here is some example SQL and the Java code to pass the current moment into the database.下面是一些示例SQL和将当前时刻传递到数据库的 Java 代码。

The example uses the H2 Database Engine , built in 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();
}

Here is a complete example app using that code.这是使用该代码的完整示例应用程序。

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();
        }
    }
}

Parsing string解析字符串

Regarding a related comment by Melnyk, here is another example based on the example code above.关于 Melnyk 的相关评论,这里是基于上述示例代码的另一个示例。 Rather than capturing the current moment, this code parses a string.这段代码不是捕获当前时刻,而是解析一个字符串。

The input string lacks any indicator of time zone or offset-from-UTC .输入字符串缺少时区offset-from-UTC 的任何指示符。 So we parse as a LocalDateTime , keeping in mind that this does not represent a moment, is not a point on the timeline.因此,我们分析的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 ldt.toString(): 2018-11-22T00:00

But we have been informed the string was meant to represent a moment in UTC, but the sender screwed up and failed to include that information (such as a Z or +00:00 on the end to mean UTC).但是我们被告知该字符串旨在表示 UTC 中的某个时刻,但发件人搞砸了并且未能包含该信息(例如末尾的Z+00:00表示 UTC)。 So we can apply an offset-from-UTC of zero hours-minutes-seconds to determine an actual moment, a specific point on the timeline.因此,我们可以应用零时分秒的 UTC 偏移量来确定实际时刻,即时间线上的特定点。 The result as a OffsetDateTime object.结果作为OffsetDateTime对象。

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

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

The Z on the end means UTC and is pronounced “Zulu”.末尾的Z表示 UTC,发音为“Zulu”。 Defined in ISO 8601 standard.在 ISO 8601 标准中定义。

Now that we have a moment in hand, we can send it to the database in a column of SQL-standard type TIMESTAMP WITH TIME ZONE .现在我们有时间了,我们可以将它发送到 SQL 标准类型TIMESTAMP WITH TIME ZONE列中的数据库。

preparedStatement.setObject( 2 , odt );

When then retrieve that stored value.然后检索该存储的值。

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

2018-11-22T00:00Z 2018-11-22T00:00Z

Here is the complete for this example app.这是此示例应用程序的完整内容。

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();
        }
    }
}

Converting转换

If you must interoperate with old code not yet updated for java.time , you can convert back-and-forth.如果必须与尚未为java.time更新的旧代码进行互操作,则可以来回转换。 Look to new methods to… / from… added to the old classes.寻找新方法to… / from…添加到旧类中。

To get a legacy java.sql.Timestamp object, call Timestamp.from( Instant ) .要获取旧的java.sql.Timestamp对象,请调用Timestamp.from( Instant ) To get an Instant from our OffsetDateTime object seen above, simply call OffsetDateTime::toInstant .要从上面看到的OffsetDateTime对象中获取Instant ,只需调用OffsetDateTime::toInstant

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

Going the other direction.走向另一个方向。

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

If using the ThreeTen-Backport library for Java 6 & 7 projects, look to the DateTimeUtils class for the to… / from… conversion methods.如果将ThreeTen-Backport库用于 Java 6 和 7 项目,请查看DateTimeUtils类以了解to… / from…转换方法。


About java.time关于java.time

The java.time framework is built into Java 8 and later. java.time框架内置于 Java 8 及更高版本中。 These classes supplant the troublesome old legacy date-time classes such as java.util.Date , Calendar , & SimpleDateFormat .这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar ,和SimpleDateFormat

To learn more, see the Oracle Tutorial .要了解更多信息,请参阅Oracle 教程 And search Stack Overflow for many examples and explanations.并在 Stack Overflow 上搜索许多示例和解释。 Specification is JSR 310 .规范是JSR 310

The Joda-Time project, now in maintenance mode , advises migration to the java.time classes.现在处于维护模式Joda-Time项目建议迁移到java.time类。

You may exchange java.time objects directly with your database.您可以直接与您的数据库交换java.time对象。 Use a JDBC driver compliant with JDBC 4.2 or later.使用符合JDBC 4.2或更高版本的JDBC 驱动程序 No need for strings, no need for java.sql.* classes.不需要字符串,不需要java.sql.*类。 Hibernate 5 & JPA 2.2 support java.time . Hibernate 5 & JPA 2.2 支持java.time

Where to obtain the java.time classes?从哪里获得 java.time 类?

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

If you want the current timestamp why not use the following function, I have used this in various projects and works perfectly:如果您想要当前时间戳,为什么不使用以下功能,我已经在各种项目中使用了它并且运行良好:

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

Example:例子:

System.out.println( getTimeStamp() );

Output: 2017-03-13 15:01:34.027输出: 2017-03-13 15:01:34.027

EDIT编辑

Using Java 8 LocalDateTime:使用 Java 8 LocalDateTime:

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

Instant always gives time in UTC whereas Timestamp gives time in your local zone. Instant 总是以 UTC 时间给出时间,而 Timestamp 给出本地时间。 So if you are not concern about any specific time zone, you can use Instant.因此,如果您不关心任何特定时区,则可以使用 Instant。 It's also a good practice to save records in UTC, so that your application remains unaffected in case it is deployed in any other timezone.以 UTC 格式保存记录也是一种很好的做法,这样您的应用程序在部署在任何其他时区时不会受到影响。

During saving a record to SQL Server DB I faced with the same problem.在将记录保存到 SQL Server DB 期间,我遇到了同样的问题。 I've used java.sql.Timestamp.valueOf(String s) to get Timestamp in UTC :我使用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);

It works for me.这个对我有用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 java.sql.Timestamp & java.time.Instant 与 JOOQ 之间的转换出现问题 - Trouble converting between java.sql.Timestamp & java.time.Instant with JOOQ 将 java.sql.Timestamp 转换为即时时间 - Converting java.sql.Timestamp to Instant Time 将毫秒时间戳反序列化为java.time.Instant - Deserialize millisecond timestamp to java.time.Instant java.time.Instant制作的java.sql.Timestamp的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 java.sql.Timestamp(long)使用哪个时区? - Which time zone is used by java.sql.Timestamp(long)? 如何在 Java 中准确地将 Epoch 时间戳转换为 java.time.Instant 类型的 object? - How to convert an Epoch timestamp into an object of type java.time.Instant Accurately in Java? 如何将java.sql.Timestamp转换为java.time.OffsetDateTime? - How to convert java.sql.Timestamp to java.time.OffsetDateTime? 将 java.sql.Timestamp 转换为 Java 8 ZonedDateTime? - Convert java.sql.Timestamp to Java 8 ZonedDateTime? 将Joda日期时间字符串转换为Java.Sql.TimeStamp以进行插入 - Convert Joda Date Time String To Java.Sql.TimeStamp for an INSERT 如何将字符串时间戳转换为java.sql.Timestamp? - How to convert String time stamp to java.sql.Timestamp?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM