如何在不使用本地时区的情况下解析日期?

How to parse date without using Local Time Zone?

从服务器我得到以下值:

epochMillis=1556532279322
iso8601=2019-04-29T10:04:39.322Z

当我执行 serverTimeDateFormat.parse(iso8601) 时,结果是 Mon Apr 29 10:04:39 GMT+02:00 2019

而对于 serverTimeDateFormat.parse(iso8601).time,结果是 1556525079322,这与我从服务器获得的结果不同(比 UNIX 时间晚 2 小时),而我在时区 + 2 小时。

当我用 serverTimeDatFormat.format(1556525079322) 重新格式化时,结果是 2019-04-29T10:04:39.322Z 我知道 SimpleDateFormat 使用的是本地时区,但为什么结果晚了 2 小时?我如何在不考虑时区的情况下解析 Date?我不明白这一切的逻辑。

我的代码:

private val serverTimeDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",Locale.ENGLISH)
val iso8601 = "2019-04-29T10:04:39.322Z" 
val epochMillis = 1556532279322
serverTimeDateFormat.parse(iso8601).time

问题在于您的 SimpleDateFormat 的模式。最后,您有 'Z',这表明要解析的日期字符串中应该有文字 "Z"。但是,日期末尾的"Z"有特殊含义,即it signifies the UTC timezone. Hence, you should parse it as a timezone designator so that the correct date value will be obtained. You can do this with the pattern XXX (See JavaDocs).

private val serverTimeDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX",Locale.ENGLISH)
val iso8601 = "2019-04-29T10:04:39.322Z" 
print( serverTimeDateFormat.parse(iso8601).time ) // 1556532279322

Runnable example on pl.kotl.in


附录:虽然上面的代码应该适合您,但如果可能的话,您应该考虑将 ThreeTen Android Backport 添加到您的项目中。这将使您能够访问由 JSR310 添加到 Java/Kotlin 的较新时间 类(在 Android API >=26 中默认也可用)。 类 通常更容易 API,并且默认使用 ISO8601,因此您根本不需要任何格式化程序:

print( ZonedDateTime.parse(iso8601).toInstant().toEpochMilli() )

避免遗留日期时间类

您使用的 糟糕 日期时间 类 多年前已被现代 java.time 取代类 在 JSR 310 中定义。

Date::toString谎言

why is the result 2 hours behind

其实还不到两个小时。

问题在于,虽然 java.util.Date 表示 UTC 中的时刻,但其 toString 方法在生成表示日期时间对象值的文本时动态应用 JVM 当前的默认时区.虽然本意是好的,但这个反特征令人困惑地产生了 Date 对象具有那个时区的错觉。

换句话说,Date::toString在撒谎。在这些遗留问题中发现的许多糟糕的设计决策之一 类。也是从不使用这些遗留物的众多原因之一 类。

java.time

Instant

解析自 UTC 中 1970 年第一时刻的纪元参考以来的毫秒数作为 Instant

Instant instant = Instant.ofEpochMilli( 1556532279322 );

你的另一个输入,一个标准的 ISO 8601 字符串,也可以被立即解析。

Instant instant = Instant.parse( "2019-04-29T10:04:39.322Z" ) ;

ZonedDateTime

要通过特定地区(时区)的人们使用的挂钟时间查看同一时刻,请应用 ZoneId 以获得 ZonedDateTime 对象。

ZoneId z = ZoneId.of( "America/Montreal" ) ;
ZonedDateTime zdt = instant.atZone( z ) ) ;

instantzdt 对象代表同一时刻。阅读同一时刻的两种方式,就像在冰岛和魁北克的 phone 上交谈的两个人同时看一眼墙上的时钟时,每个人都会看到不同的时间。