在Java中将时间戳转换为即时消息会增加不必要的时间偏移

时间:2017-06-08 18:41:21

标签: java mysql datetime utc timezone-offset

我需要能够转换从MySQL数据库获取的数据,该数据库存储在" datetime"将字段转换为Java ZonedDateTime对象。

ZonedDateTime dt = ZonedDateTime.ofInstant(rs.getTimestamp("Start").toInstant(), UTC_ZONE_ID)

我遇到的问题是,toInstant()将{i} 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());

我明白了:

2017-06-08 13:15:00.0
2017-06-08T17:15:00Z

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

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

3 个答案:

答案 0 :(得分: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.

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

不要相信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():America / Montreal

     

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

避免遗留日期时间类

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

您的JDBC 4.2兼容driver可以通过调用PreparedStatement::setObjectResultSet::getObject直接解决java.time类型问题。

myPreparedStatement.setObject( … , instant ) ;

......和......

Instant instant = myResultSet.getObject( … , Instant.class ) ;

如果使用尚未更新到JDBC 4.2和java.time的JDBC驱动程序,请使用添加到旧类的新方法简要地转换为java.sql.Timestampfrom ( 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.DateCalendar和& SimpleDateFormat

现在位于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

答案 1 :(得分:2)

java.sql.Timestamp这样的旧类在设计上存在很多问题。经常引起混淆的事情之一是Timestamp.toString()在JVM的时区中打印时间,即使其中的时间Timestamp只保留一个没有时区的时间点。具体来说,当Timestamp等于2017-06-08T17:15:00Z(UTC)时,您将在运行匹兹堡时间的计算机上打印(在一年的这个时候,它的偏移量为-4:00)从UTC),隐式调用Timestamp.toString(),它读取JVM的时区并将时间打印为13:15:00.0,因为宾夕法尼亚州的这个时间等于Timestamp中的UTC时间。

所以简而言之,请不要担心,TimestampInstant都是正确的。

答案 2 :(得分:0)

我认为此代码段可以回答您的问题。这将在本地时区中接收String,将其转换为UTC,并将其存储在db中。

    //Getting the LocalDateTime Objects from String values
    DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd kk:mm"); 
    String txtStartTime = "2017-03-29 12:00";

    LocalDateTime ldtStart = LocalDateTime.parse(txtStartTime, df);


    //Convert to a ZonedDate Time in UTC
    ZoneId zid = ZoneId.systemDefault();

    ZonedDateTime zdtStart = ldtStart.atZone(zid);
    System.out.println("Local Time: " + zdtStart);
    ZonedDateTime utcStart = zdtStart.withZoneSameInstant(ZoneId.of("UTC"));
    System.out.println("Zoned time: " + utcStart);
    ldtStart = utcStart.toLocalDateTime();
    System.out.println("Zoned time with zone stripped:" + ldtStart);
    //Create Timestamp values from Instants to update database
    Timestamp startsqlts = Timestamp.valueOf(ldtStart); //this value can be inserted into database
    System.out.println("Timestamp to be inserted: " +startsqlts);

    //insertDB(startsqlts);