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

时间:2017-03-13 14:54:18

标签: java date time timestamp java.time.instant

在我正在开发的应用程序中,我需要将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(Instnant.now(), ZoneOffset.UTC);
Timestamp current = Timestamp.valueOf(ldt);

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

5 个答案:

答案 0 :(得分:34)

我将计算机的时区更改为欧洲/布加勒斯特进行实验。这是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 Timestamp打印DateFormat时,我们可以看到它是太早2小时,04:16:32 UTC Instant是06:16 :32 UTC所以这种方法是骗人的,看起来它有效,但事实并非如此。

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

答案 1 :(得分:8)

使用错误的类

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。继续阅读以获取更多信息。

Table of date-time types in Java (both modern and legacy) and in standard SQL.

当前时刻

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

Instant instant = Instant.now() ;

…或…

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

两者都代表着同一件事,在UTC时刻。

数据库

这里有一些示例SQL和将当前时刻传递到数据库中的Java代码。

该示例使用Java内置的H2 Database Engine

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的相关评论,这是基于上面示例代码的另一个示例。该代码不是捕获当前时刻,而是解析字符串。

输入字符串缺少任何time zoneoffset-from-UTC的指示符。因此,我们将其解析为LocalDateTime,请记住,这 not 并不代表时刻,而是 not 在时间轴上的一点。

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偏移零小时-分钟-秒来确定实际时刻,即时间轴上的特定点。结果为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

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

目前位于Joda-Timemaintenance mode项目建议迁移到java.time类。

要了解更多信息,请参见Oracle Tutorial。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

您可以直接与数据库交换 java.time 对象。使用符合JDBC driver或更高版本的JDBC 4.2。不需要字符串,不需要java.sql.*类。

在哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。该项目为将来可能在java.time中添加内容提供了一个试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

答案 2 :(得分:1)

如果您想要当前的时间戳,为什么不使用以下功能,我已经在各种项目中使用它并且工作得很好:

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

答案 3 :(得分:1)

Instant 总是以 UTC 时间给出时间,而 Timestamp 给出本地时间。 因此,如果您不关心任何特定时区,则可以使用 Instant。 以 UTC 格式保存记录也是一种很好的做法,这样您的应用程序在部署在任何其他时区时不会受到影响。

答案 4 :(得分:0)

在将记录保存到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);

对我有用。