【问题标题】:Converting time to UTC time goes the opposite way将时间转换为 UTC 时间则相反
【发布时间】:2019-06-16 06:42:13
【问题描述】:

我正在尝试使用 Java 8 DateTimeFormatter 解析偏移时间。

我生活在美国东部标准时间 UTC-5,所以当我尝试转换时

2019-01-22T13:09:54.620-05:00 应该是 --> 2019-01-22T18:09:54.620

但是,使用我的代码,它获取当前时间并返回 5 小时,结果为 2019-01-22 08:09:54.620

代码:


import java.sql.Timestamp
import java.time._
import java.time.format.DateTimeFormatter

import scala.util.{Failure, Success, Try}

class MyTimeFormatter(parser: DateTimeFormatter) {

   def parse(input: String): Try[Timestamp] = {
    Try(new Timestamp(Instant.from(parser.withZone(ZoneOffset.UTC).parse(input)).toEpochMilli))
  }
}

测试:

new MyTimeFormatter(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSxxx")).parse("2019-01-22T13:09:54.620-05:00") shouldEqual Timestamp.valueOf("2019-01-22T18:09:54.620")

解析器的类型为DateTimeFormatter,输入字符串只是"2019-01-22T13:09:54.620-05:00"

我想使用这个 parser.parse 方法,而不是像 OffsetDateTime.parse(input, parser) 这样的特定 temporalAccessors,所以我可以处理像 LocalTime, LocalDateTime, ZonedDateTime, OffsetDateTime, etc.. 这样的所有情况

似乎代码只是获取时间,减去偏移量,并将其标记为 UTC,而不是计算相对于 UTC 的偏移量。

另外,只有当输入格式为 ZonedDateTime/OffsetDateTime 格式时,是否有办法应用此 UTC 转换?如果我输入一个 LocalDateTime(没有偏移量),例如 2017-01-01 12:45:00,解析器仍将应用 UTC 偏移量转换,因为我告诉解析器使用区域 UTC 进行解析。

【问题讨论】:

标签: java scala datetime-format


【解决方案1】:

tl;博士

使用现代的 java.time 类。仅在需要使用旧代码时才转换为遗留类。

具体来说,将您的输入字符串解析为OffsetDateTime 对象,通过提取Instant 调整为UTC,最后,转换为java.sql.Timestamp(仅在必须时)。

java.sql.Timestamp ts =                           // Avoid using this badly-designed legacy class if at all possible.
    Timestamp                                     // You can convert back-and-forth between legacy and modern classes.
    .from(                                        // New method added to legacy class to convert from modern class.
        OffsetDateTime                            // Represents a moment with an offset-of-UTC, a number of some hours-minutes-seconds ahead or behind UTC.
       .parse( "2019-01-22T13:09:54.620-05:00" )  // Text in standard ISO 8601 format can be parsed by default, without a formatting pattern.
       .toInstant()                               // Adjust from an offset to UTC (an offset of zero) by extracting an `Instant`. 
    )                                             // Returns a `Timestamp` object. Same moment as both the `OffsetDateTime` and `Instant` objects.
;

看到这个code run live at IdeOne.com,结果:

ts.toString(): 2019-01-22 18:09:54.62

如果使用 JDBC 4.2 或更高版本,请完全跳过 Timestamp

myPreparedStatement.setObject( … , myOffsetDateTime ) ;

祖鲁语

2019-01-22T13:09:54.620-05:00 应该是 --> 2019-01-22T18:09:54.620

如果您的意思是第二个值代表 UTC 中的某个时刻,请附加 offset-from-UTC 以指示该事实。 +00:00Z(发音为“Zulu”):2019-01-22T18:09:54.620Z

报告没有与 UTC 偏移或时区指示符的时刻就像报告没有货币指示符的金额。

OffsetDateTime

一个与 UTC 有偏移的字符串应该被解析为一个 OffsetDateTime 对象。

您的输入字符串恰好符合文本日期时间值的 ISO 8601 标准格式。 java.time 类在解析/生成字符串时默认使用 ISO 8601 格式。所以不需要指定格式模式。

OffsetDateTime odt = OffsetDateTime.parse( "2019-01-22T13:09:54.620-05:00" ) ;

Timestamp

显然你想要一个java.sql.Timestamp 对象。这是与 Java 的最早版本捆绑在一起的可怕的日期时间类之一。这些类现在是遗留类,完全被采用 JSR 310 的现代 java.time 类所取代。尽可能避免使用这些遗留类。

如果您必须有一个Timestamp 才能与尚未更新的旧代码互操作以使用 java.time,您可以转换。要进行转换,请调用添加到 old 类的新方法。

Instant

java.sql.Timestamp 类带有一个from( Instant ) 方法。 Instant 是 UTC 中的一个时刻。要从 OffsetDateTime 的偏移量调整为 UTC,只需提取一个 Instant

Instant instant = odt.toInstant() ;
java.sql.Timestamp ts = Timestamp.from( instant ) ;

我们有三个对象(odtinstant、&ts)都代表同一个时刻。第一个有不同的挂钟时间。但是这三个都是时间轴上的同一点。

JDBC 4.2

从 JDBC 4.2 开始,我们可以直接与数据库交换 java.time 对象。所以不需要使用Timestamp

myPreparedStatement.setObject( … , odt ) ;

……和……

OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime.class ) ;

关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

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 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

    【解决方案2】:

    虽然我无法准确重现您的问题(即使将时钟更改为 EST),但我正在观察:

    Instant instant = Instant.from(parser.withZone(ZoneOffset.UTC).parse("2019-01-22T13:09:54.620-05:00"));
    

    这正在产生您期望的时间 (2019-01-22T18:09:54.620Z)。

    Timestamp ts = new Timestamp(instant.toEpochMilli());
    

    因为这是基于java.util.Date,显示为您的当地时间。

    Instant 转换为Timestamp 的更好方法是通过LocalDateTime,如下所示:

    Timestamp ts = Timestamp.valueOf(instant.atZone(ZoneOffset.UTC).toLocalDateTime());
    

    【讨论】:

    • LocalDateTime 有效!这似乎是一个不幸的问题,因为 Timestamp 似乎与 Java 8 时间不兼容,但我需要在使用数据库时使用 Timestamp。如果input 包含偏移量,您是否也知道我是否只能应用此UTC 转换?我在问题的最后一段中问了这个
    • 我不清楚您要问什么,但作为本网站的一般规则,请将每个问题限制为一个问题。您可能希望将第二项作为单独的问题提出。
    • 不正确。 LocalDateTime 缺少任何时区或与 UTC 偏移的概念。因此,根据定义,它不能代表片刻。 LocalDateTime不是时间线上的一个点。 LocalDateTime 代表大约 26-27 小时(全球时区范围)范围内的潜在时刻。 LocalDateTime 正是您应该在这里使用的类。
    • @BasilBourque 同样适用于Timestamp
    • 不,java.sql.Timestamp 代表时刻,时间线上的特定点。不幸的是,其设计不佳的toString 方法没有提及与UTC 或时区的偏移量。但是,它有一个,不像LocalDateTime
    猜你喜欢
    • 2017-04-27
    • 2018-07-27
    • 2021-09-05
    • 1970-01-01
    • 2013-02-03
    • 1970-01-01
    • 2014-05-06
    • 2014-07-18
    相关资源
    最近更新 更多