![](/img/trans.png)
[英]Trouble converting between java.sql.Timestamp & java.time.Instant with JOOQ
[英]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
当你的意思是一个特定的时刻,在时间轴上的特定点。 而是使用:
Instant
(总是在 UTC)OffsetDateTime
(带有时间和UTC偏移量的日期)ZonedDateTime
(带有时间和时区的日期)。然后我尝试创建 Timestamp 对象
别。
永远不要使用java.sql.Timestamp
。 替换为java.time.Instant
。 继续阅读以获取更多信息。
要捕获 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 8 及更高版本中。 这些类取代麻烦的老传统日期时间类,如java.util.Date
, Calendar
,和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 类?
如果您想要当前时间戳,为什么不使用以下功能,我已经在各种项目中使用了它并且运行良好:
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.