Java - 在没有区域偏移的情况下将 java.time.Instant 转换为 java.sql.Timestamp
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/42766674/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java - Convert java.time.Instant to java.sql.Timestamp without Zone offset
提问by Aleydin Karaimin
In the application I am developing, I need to convert java.time.Instant
object to java.sql.Timestamp
. When I create Instant
object like:
在我正在开发的应用程序中,我需要将java.time.Instant
对象转换为java.sql.Timestamp
. 当我创建Instant
对象时:
Instant now = Instant.now();
I receive something like 2017-03-13T14:28:59.970Z
. And when I try to create Timestamp
object like this:
我收到类似2017-03-13T14:28:59.970Z
. 当我尝试创建这样的Timestamp
对象时:
Timestamp current = Timestamp.from(now);
I receive something like 2017-03-13T16:28:59.970Z
. The same result but with an additional 2 hour delay.
Can someone explain why this is happening and provide me with an answer to fix this problem without this delay?
我收到类似2017-03-13T16:28:59.970Z
. 相同的结果,但额外延迟了 2 小时。有人可以解释为什么会发生这种情况,并为我提供解决此问题的答案,而不会延迟吗?
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
object来做到这一点?
采纳答案by Ole V.V.
I changed my computer's time zone to Europe/Bucharest for an experiment. This is UTC + 2 hours like your time zone.
为了进行实验,我将计算机的时区更改为 Europe/Bucharest。这是 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). 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. Only because of this, the displays are different.
现在你可以看到Timestamp
真的与Instant
我们开始的一致(只有毫秒没有打印,但我相信它们也在那里)。所以你已经正确地完成了所有事情,只是感到困惑,因为当我们打印时,Timestamp
我们隐含地调用了它的toString
方法,而这个方法反过来获取计算机的时区设置并显示该时区中的时间。正因为如此,显示方式不同。
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. So this method is deceiving, it looks like it's working, but it doesn't.
现在,当我们Timestamp
使用我们的 UTC打印时DateFormat
,我们可以看到它早了 2 小时,即 06:16:32 UTC 时Instant
04:16:32 UTC。所以这个方法是骗人的,看起来有效,但实际上并没有。
This shows the trouble that lead to the design of the Java 8 date and time classes to replace the old ones. 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. I don't know if that's available for you just yet, but I'm convinced it will be.
这显示了导致设计 Java 8 日期和时间类以替换旧的类的麻烦。因此,解决您的问题的真正和好的解决方案可能是让自己拥有一个可以轻松接受Instant
对象的 JDBC 4.2 驱动程序,这样您就可以避免Timestamp
完全转换为。我不知道这是否适合您,但我相信它会。
回答by DazstaV3
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());
}
回答by Taras Melnyk
During saving a record to SQL Server DB I faced with the same problem. I've used java.sql.Timestamp.valueOf(String s)
to get Timestampin UTC:
在将记录保存到 SQL Server DB 期间,我遇到了同样的问题。我曾经 在UTC 中java.sql.Timestamp.valueOf(String s)
获取时间戳:
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);
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.
这个对我有用。
回答by Basil Bourque
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.timeclasses (LocalDateTime
here) with the terrible old legacy date-time classes (java.sql.Timestamp
here). The java.timeframework supplants entirely the terrible old classes, as of the adoption of JSR 310. You need never use Timestamp
again: As of JDBC 4.2we can directly exchange java.timeobjects with the database.
首先,永远不要将现代java.time类(LocalDateTime
此处)与糟糕的旧式日期时间类(java.sql.Timestamp
此处)混合在一起。该java.time框架完全取代了旧的可怕类,采用的JSR 310。您再也不需要使用Timestamp
了:从JDBC 4.2 开始,我们可以直接与数据库交换java.time对象。
The other problem, is that the LocalDateTime
class cannot, by definition, represent a moment. It purposely lacks a time zone or offset-from-UTC. Use LocalDateTime
only when you mean a date with time-of-day everywhereor 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
,根据定义,类不能代表片刻。它故意缺少时区或 UTC 偏移量。LocalDateTime
仅当您的意思是在任何地方或任何地方都有时间的日期时才使用,换句话说,在大约 26-27 小时(当前全球时区的当前极端)范围内的任何/所有更多时刻。
Do notuse LocalDateTime
when you mean a specific moment, a specific point on the timeline. Instead use:
千万不能使用 LocalDateTime
时,你的意思是一个特定的时刻,在时间轴上的特定点。而是使用:
Instant
(always in UTC)OffsetDateTime
(a date with time-of-day and with offset-from-UTC)ZonedDateTime
(a date with time-of-day and with a time zone).
Instant
(总是在 UTC)OffsetDateTime
(带有时间和UTC偏移量的日期)ZonedDateTime
(带有时间和时区的日期)。
Then I try to create Timestamp object
然后我尝试创建 Timestamp 对象
Don't.
别。
Never use java.sql.Timestamp
. Replaced by java.time.Instant
. Read on for more info.
永远不要使用java.sql.Timestamp
. 替换为java.time.Instant
。继续阅读以获取更多信息。
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 SQLand 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. Rather than capturing the current moment, this code parses a string.
关于 Melnyk 的相关评论,这里是基于上述示例代码的另一个示例。这段代码不是捕获当前时刻,而是解析一个字符串。
The input string lacks any indicator of time zoneor offset-from-UTC. So we parse as a LocalDateTime
, keeping in mind that this does notrepresent a moment, is nota point on the timeline.
输入字符串缺少时区或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
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). So we can apply an offset-from-UTC of zero hours-minutes-seconds to determine an actual moment, a specific point on the timeline. The result as a OffsetDateTime
object.
但是我们已被告知该字符串旨在表示 UTC 中的某个时刻,但发件人搞砸了并且未能包含该信息(例如 aZ
或+00:00
末尾表示 UTC)。因此,我们可以应用零时分秒的 UTC 偏移量来确定实际时刻,即时间线上的特定点。结果作为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”. Defined in ISO 8601 standard.
在Z
上月底表示UTC发音为“祖鲁”。在 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. Look to new methods to…
/from…
added to the old classes.
如果必须与尚未为java.time更新的旧代码进行互操作,则可以来回转换。寻找新方法to…
/from…
添加到旧类中。
To get a legacy java.sql.Timestamp
object, call Timestamp.from( Instant )
. To get an Instant
from our OffsetDateTime
object seen above, simply call OffsetDateTime::toInstant
.
要获取遗留java.sql.Timestamp
对象,请调用Timestamp.from( Instant )
。要从上面看到Instant
的OffsetDateTime
对象中获取,只需调用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-Backportlibrary 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.timeframework is built into Java 8 and later. These classes supplant the troublesome old legacydate-time classes such as java.util.Date
, Calendar
, & SimpleDateFormat
.
该java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date
, Calendar
, & SimpleDateFormat
。
The Joda-Timeproject, now in maintenance mode, advises migration to the java.timeclasses.
现在处于维护模式的Joda-Time项目建议迁移到java.time类。
To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations. Specification is JSR 310.
要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310。
You may exchange java.timeobjects directly with your database. Use a JDBC drivercompliant with JDBC 4.2or later. No need for strings, no need for java.sql.*
classes.
您可以直接与您的数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC 驱动程序。不需要字符串,不需要类。java.sql.*
Where to obtain the java.time classes?
从哪里获得 java.time 类?
- Java SE 8, Java SE 9, Java SE 10, Java SE 11, and later - Part of the standard Java API with a bundled implementation.
- Java 9 adds some minor features and fixes.
- Java SE 6and Java SE 7
- Most of the java.timefunctionality is back-ported to Java 6 & 7 in ThreeTen-Backport.
- Android
- Later versions of Android bundle implementations of the java.timeclasses.
- For earlier Android (<26), the ThreeTenABPproject adapts ThreeTen-Backport(mentioned above). See How to use ThreeTenABP….
- Java SE 8、Java SE 9、Java SE 10、Java SE 11及更高版本 - 标准 Java API 的一部分,具有捆绑实现。
- Java 9 添加了一些小功能和修复。
- Java SE 6和Java SE 7
- 大部分java.time功能在ThreeTen-Backport中向后移植到 Java 6 & 7 。
- 安卓
- java.time类的更高版本的 Android 捆绑实现。
- 对于早期的 Android(<26),ThreeTenABP项目采用了ThreeTen-Backport(上面提到过)。请参阅如何使用ThreeTenABP ...。
The ThreeTen-Extraproject extends java.time with additional classes. This project is a proving ground for possible future additions to java.time. You may find some useful classes here such as Interval
, YearWeek
, YearQuarter
, and more.
该ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如Interval
,YearWeek
,YearQuarter
,和更多。