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.
之后,您可能会看到问题(或非问题)。如果没有,请编辑您的问题以显示每个变量的调试值。
不要相信Timestamp::toString
重要提示:java.sql.Timestamp::toString 方法的谎言。该方法在生成字符串时应用 JVM 当前的默认时区。实际值始终采用 UTC。避免这些麻烦的遗留类的众多原因之一。在您自己的机器上运行以下代码示例,以查看您的默认时区对Timestamp 的文本表示的影响。
让我们模拟一下code running live in 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.
当前默认时区:太平洋/奥克兰
当前默认的 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 兼容driver 可以通过调用PreparedStatement::setObject 和ResultSet::getObject 直接寻址java.time 类型。
myPreparedStatement.setObject( … , instant ) ;
……和……
Instant instant = myResultSet.getObject( … , Instant.class ) ;
如果使用尚未更新到 JDBC 4.2 和 java.time 的 JDBC 驱动程序,请使用添加到旧类的新方法短暂转换为 java.sql.Timestamp:from ( Instant )、toInstant() 等。但除了与数据库交换数据之外,还可以在 java.time 对象中完成所有实际工作(业务逻辑)。
myPreparedStatement.setTimestamp( … , java.sql.Timestamp.from( instant ) ) ;
……和……
Instant instant = myResultSet.getTimestamp( … ).toInstant() ;
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date、Calendar 和 SimpleDateFormat。
Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。
要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。
您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。
从哪里获得 java.time 类?
ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如Interval、YearWeek、YearQuarter 和more。