如何不使用本地时区解析日期?时区导致时间戳差异问题排查
这问题的根源出在你对SimpleDateFormat格式字符串里的'Z'处理上——你把它当成了普通文本字符,而不是UTC时区的标记,这就导致了解析逻辑的偏差。
为什么时间戳会滞后2小时?
你的格式字符串是"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",这里的'Z'被单引号包裹,意味着SimpleDateFormat只会把它当作一个普通的字母"Z"来处理,完全忽略它代表UTC时区的含义。
当你解析2019-04-29T10:04:39.322Z时,程序实际上是在解析2019-04-29T10:04:39.322(相当于去掉了Z),并且使用**你的本地时区(GMT+2)**来解析这个时间。这时候解析出的Date对象代表的是GMT+2时区的"2019-04-29 10:04:39",而这个时间对应的UTC时间是"2019-04-29 08:04:39"——对应的时间戳就是1556525079322,和服务器返回的UTC时间戳1556532279322正好差了2小时(服务器的时间是UTC的10:04:39)。
如何正确解析(不被本地时区干扰)?
你需要让SimpleDateFormat识别Z作为UTC时区的标记,或者直接使用更可靠的现代日期API,这里有几种方案:
方案1:使用ISO 8601时区格式符(Java 7+)
把格式字符串里的'Z'换成X,X是专门匹配ISO 8601时区偏移的格式符(包括Z表示UTC):
private val serverTimeDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.ENGLISH)
这样解析时,程序会自动把Z识别为UTC时区,解析出的Date对象对应的时间戳就和服务器返回的一致了。
方案2:显式设置时区为UTC(兼容Java 6及以下)
如果你需要兼容旧版本Java,可以去掉Z的单引号,然后把SimpleDateFormat的时区强制设置为UTC:
private val serverTimeDateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.ENGLISH).apply { timeZone = TimeZone.getTimeZone("UTC") }
这里的Z格式符会匹配时区偏移,而设置时区为UTC后,解析时会把输入的时间当作UTC时间处理,不受本地时区影响。
更推荐的方案:使用Java 8+的java.time API
SimpleDateFormat是线程不安全的,而且设计上有很多坑。Java 8引入的java.time包(JSR-310)是更现代、更可靠的日期时间处理方案,直接用Instant就能完美解析ISO 8601格式的UTC时间:
import java.time.Instant val iso8601 = "2019-04-29T10:04:39.322Z" val instant = Instant.parse(iso8601) val epochMillis = instant.toEpochMilli() // 结果就是1556532279322,和服务器返回完全一致
Instant本身就是以UTC为基准的时间戳,解析带Z的字符串时会自动识别为UTC,完全不需要担心时区问题。
内容的提问来源于stack exchange,提问作者Artur A

