在Java中将时间戳转换为即时添加了不必要的时间偏移

AnK*_*ing 1 java mysql datetime utc timezone-offset

我需要能够将存储在"datetime"字段中的MySQL数据库中提取的数据转换为Java ZonedDateTime对象.

ZonedDateTime dt = ZonedDateTime.ofInstant(rs.getTimestamp("Start").toInstant(), UTC_ZONE_ID)
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是在我不需要toInstant()Timestamp对象上添加本地时间偏移,因为日期时间已经以UTC格式存储在数据库中.所以当我运行以下代码时:

ZonedDateTime startDT = 
        ZonedDateTime.ofInstant(rs.getTimestamp("Start").toInstant(),Globals.LOCALZONEID);
System.out.println(rs.getTimestamp("start"));
System.out.println(rs.getTimestamp("start").toInstant());
Run Code Online (Sandbox Code Playgroud)

我明白了:

2017-06-08 13:15:00.0
2017-06-08T17:15:00Z
Run Code Online (Sandbox Code Playgroud)

我需要时间组件保持不变.

我无法找到任何明显的问题解决方案,所以我在这里遗漏了什么?

Bas*_*que 13

Timestamp&Instant总是UTC

我遇到的问题是.toInstant()将本地时间偏移量添加到Timestamp对象

不,不是的.

也不能分配任何其他区域.

不要将代码汇总到一行.将每个步骤分成单独的行,以便您可以调试它们的值.

java.sql.Timestamp ts = rs.getTimestamp("Start") ;  // Actually in UTC, but it's `toString` method applies JVM’s current default time zone while generating string.
Instant instant = ts.toInstant() ;                  // Same moment, also in UTC.
ZoneId z = ZoneId.of( "America/Montreal" ) ;        // Or call your global var: `Globals.LOCALZONEID`.
ZonedDateTime zdt = instant.atZone( z );            // Same moment, same point on timeline, but with wall-clock time seen in a particular zone.
Run Code Online (Sandbox Code Playgroud)

之后,您可能会看到问题(或非问题).如果没有,请编辑您的问题以显示每个变量的调试值.

不要相信 Timestamp::toString

重要提示:java.sql.Timestamp::toString方法所在.该方法在生成字符串时应用JVM的当前默认时区.实际值始终为UTC.避免这些麻烦遗留类的众多原因之一.在您自己的计算机上运行以下代码示例,以查看默认时区对文本表示的影响Timestamp.

让我们对IdeOne.com中运行的代码进行模拟.IdeOne.com上的JVM默认为UTC/GMT,因此我们通过Pacific/Auckland任意指定默认值来覆盖默认值.

Instant now = Instant.now() ;                       // Simulating fetching a `Timestamp` from database by using current moment in UTC.

TimeZone.setDefault( TimeZone.getTimeZone( "Pacific/Auckland" ) ) ;
ZoneId zoneIdDefault = ZoneId.systemDefault() ;
ZoneOffset zoneOffset = zoneIdDefault.getRules().getOffset( now ) ;

java.sql.Timestamp ts = java.sql.Timestamp.from( now ) ;  // Actually in UTC, but it's `toString` method applies JVM’s current default time zone while generating string.
Instant instant = ts.toInstant() ;                  // Same moment, also in UTC.
ZoneId z = ZoneId.of( "America/Montreal" ) ;        // Or call your global var: `Globals.LOCALZONEID`.
ZonedDateTime zdt = instant.atZone( z );            // Same moment, same point on timeline, but with wall-clock time seen in a particular zone.
Run Code Online (Sandbox Code Playgroud)

目前默认时区:太平洋/奥克兰

当前默认偏离UTC:太平洋/奥克兰| 总秒数:43200

now.toString():2017-06-09T04:41:10.750Z

ts.toString():2017-06-09 16:41:10.75

instant.toString():2017-06-09T04:41:10.750Z

z.toString():美国/蒙特利尔

zdt.toString():2017-06-09T00:41:10.750-04:00 [美国/蒙特利尔]

避免遗留日期时间类

在java.time包之外找到的旧日期时间类很麻烦,令人困惑,设计糟糕,有缺陷.尽可能避免使用它们.这包括java.sql.Timestamp.

您的JDBC 4.2兼容驱动程序可以通过调用PreparedStatement::setObject和直接解决java.time类型ResultSet::getObject.

myPreparedStatement.setObject( … , instant ) ;
Run Code Online (Sandbox Code Playgroud)

......而且......

Instant instant = myResultSet.getObject( … , Instant.class ) ;
Run Code Online (Sandbox Code Playgroud)

如果使用尚未更新到JDBC 4.2和java.time的JDBC驱动程序,请简单地转换为java.sql.Timestamp使用添加到旧类的新方法:from ( Instant ),toInstant()等.但是,除了与数据库交换数据之外,还要在java.time对象中完成所有实际工作(业务逻辑).

myPreparedStatement.setTimestamp( … , java.sql.Timestamp.from( instant ) ) ;
Run Code Online (Sandbox Code Playgroud)

......而且......

Instant instant = myResultSet.getTimestamp( … ).toInstant() ;
Run Code Online (Sandbox Code Playgroud)

关于java.time

java.time框架是建立在Java 8和更高版本.这些类取代麻烦的老传统日期时间类,如java.util.Date,Calendar,和SimpleDateFormat.

现在处于维护模式Joda-Time项目建议迁移到java.time类.

要了解更多信息,请参阅Oracle教程.并搜索Stack Overflow以获取许多示例和解释.规范是JSR 310.

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

从哪里获取java.time类?

ThreeTen-额外项目与其他类扩展java.time.该项目是未来可能添加到java.time的试验场.您可以在此比如找到一些有用的类Interval,YearWeek,YearQuarter,和更多.