【问题标题】:ZonedDateTime.toInstant().toEpochMilli() losing zone dataZonedDateTime.toInstant().toEpochMilli() 丢失区域数据
【发布时间】:2020-11-02 20:03:35
【问题描述】:

我正在使用在系统时区返回 Date 的 TrueTime 库。当转换为毫秒时,我无法将此 Date 转换为 UTC 日期。

这是我所做的:

// getting true time in GMT [ex : 2020-07-13T18:00:57.192+03:00 [Europe/Istanbul]]
Date now = getTrueNowTimeInGMT();

// I like to use `LocalDateTime`, so I am converting `Date` to `LocalDateTime`:
LocalDateTime ldtOfSystem = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());

// Converting system default zone to UTC
ZonedDateTime zdtOfSystem = ldtOfSystem.atZone(ZoneId.systemDefault());
ZonedDateTime zdtOfSystemInUTC = zdtOfSystem.withZoneSameInstant(ZoneId.of("UTC"));

// Making sure that it is in the UTC. It is returning correct UTC time [ex: 2020-07-13T15:00:57.192 [UTC]]
System.out.println(zdtOfSystemInUTC); 

// converting zdtOfSystemInUTC to milliseconds - problem is occurring here!
zdtOfSystemInUTC.toInstant().toEpochMilli();

我正在丢失区域数据,它会根据本地区域(系统区域)返回毫秒数。我再次将这些毫秒转换为日期,结果为 GMT+3 [2020-07-13T18:00:57.192+03:00]

我错过了什么吗?我在一篇文章中读到toInstant() 方法不关心时区。如何从我在ZonedDateTime 中指出的特定时区获取毫秒

编辑感谢@MenuHochSchild 指出,我更改了变量名(希望改成更好的)。

说明。为什么我需要 UTC 时间? 我无法控制用户的时间。他/她可以轻松更改他/她设备的日期和时间,这给我们带来了麻烦。所以为了解决这个问题,我们找到了一个名为 TrueTime 的库,它提供了这样的实时Date 对象:

Tue Jul 14 00:32:46 GMT+03:00 2020

为了与我们的服务器同步并进行一些与时间相关的操作,我需要将这个Date GMT 转换为UTC 并将其转换为毫秒。

【问题讨论】:

  • 您的 ldtInGMT 名称不正确,因为它已经是您系统时区中的分区时间戳,而不是 GMT。您的声明 returning milliseconds based on the local zone (system zone) 是错误的,因为方法 toEpochMilli() 只返回相对到 UTC。
  • 当然,如果您只保存/读取自 Unix 纪元以来的毫秒数(相对于 UTC 区域),那么您自然会丢失区域信息,因为您只保留瞬间/时刻。
  • 添加您正在使用的 TrueTime 库的链接。
  • 当您说“为什么我需要 UTC 时间?”时,您的意思是“为什么我需要从远程时间服务器获取当前时刻?”

标签: android java-8 java-time java.time.instant


【解决方案1】:

你似乎误解了一两件事。

// getting true time in GMT [ex : 2020-07-13T18:00:57.192+03:00 [Europe/Istanbul]]
Date now = getTrueNowTimeInGMT();

Date 没有任何时区或与 UTC 或 GMT 的偏移量。所以它不能在格林威治标准时间。 Date 是一个时间点,仅此而已。顺便说一句,我通常建议您根本不要使用该类,因为它设计不佳且早已过时;但如果 TrueTime 还不能为您提供 Instant 或其他现代课程,我们现在只能使用 Date

我正在丢失区域数据,它返回的毫秒数基于 本地区域(系统区域)。 …

自纪元以来的毫秒计数与时区无关。纪元通常以 UTC 定义,但它是 一个 时间点。因此,毫秒永远不会基于任何时区(或者您可能会说它们始终基于 UTC,具体取决于您喜欢如何使用这些词,意思相同)。

…我再次将这些毫秒转换为日期,结果是 在 GMT+3 [2020-07-13T18:00:57.192+03:00]

这部分我没看懂。您引用的日期、时间和 UTC 偏移量与您从 TrueTime 获得的日期、时间和 UTC 偏移量相同,那么它怎么可能出错呢? Java 将 UTC 和 GMT 视为同义词(在现实世界中,UTC 和 GMT 之间的时间总是不到一秒,所以这可能没问题)。

仍然假设您无法从您的日期和时间库中获取老式的Date,但在我看来,获取自纪元以来当前毫秒数的简单方法是:

getTrueNowTimeInGMT().getTime();

因为我上面说过,无论设备的时区设置如何,这都是可靠的。

【讨论】:

  • 感谢 Ole 的详细说明。由于我对 GMT、UTC 和 java 的 Date 包背后的哲学缺乏了解,我现在可以看到我发布的问题相当具有误导性。
  • "自纪元以来的毫秒计数与时区无关。"不完全正确。纪元参考 必须 本身以某个时区的形式来说明,以便具有任何意义。这就是Instant 类Javadoc 至少15 次提到UTC 的原因。这就是为什么当人们说“Instant 没有时区”时,我觉得它会分散注意力、无益且相当迂腐。 Instant 类仅在与 1970 年第一刻 UTC 之间的时间距离方面才有意义。因此,我认为“即时代表 UTC 中的一个时刻”是完全公平的。
【解决方案2】:

Answer by Ole V.V. 是正确的。我将添加一些想法和图表。

永远不要使用java.util.Date

您的代码:

现在日期 = getTrueNowTimeInGMT();

查看您的库是否已针对 java.time 进行了更新。 java.util.Date 类在几年前被 JSR 310 所取代。应该改用java.time.Instant 类。

Instant 代表 UTC 时间

收到Date 对象后,立即从遗留类转换为现代类。使用添加到旧类中的新转换方法。

Instant instant = myJavaUtilDate.toInstant() ;

你说:

我错过了什么吗?我在一篇文章中读到 toInstant() 方法不关心时区。

java.time.Instant 不关心时区,因为它始终代表 UTC 定义的时刻。

  • 通过Instant.now 捕捉当前时刻将始终以UTC 的角度呈现。
  • 通过Instant::plusInstant::minus 添加/减去时间跨度将始终采用UTC,并且涉及通用的24 小时UTC 时间,不会遇到政治时间异常,例如@987654323 @。

从纪元参考计数

这两个类,传统的和现代的,都代表了 UTC 的一个时刻。自 1970 年第一刻 UTC 1970-01-01T00:00Z 以来,两者都在内部保持时间计数。 Instant 在计数中使用了更精细的纳秒分辨率,而 Date 使用毫秒。

显然,您想要计算自 1970-01-01T00:00Z 以来的毫秒数。

long millisSinceEpoch = instant.toEpochMilli() ;

又回来了。

Instant instant = Instant.ofEpochMilli( millisSinceEpoch ) ;

我确实建议将跟踪时间作为计数。这样的计数对于人类读者来说本质上是模棱两可的,并且容易出现错误或误解。我强烈建议改用标准 ISO 8601 字符串来传达日期时间值。

String output = instant.toString() ;

又回来了。

Instant instant = Instant.parse( input ) ;

ZonedDateTime

你说:

如何获取我在 ZonedDateTime 中指出的特定时区的毫秒数?

ZonedDateTime 对象代表特定地区人们使用的挂钟时间所看到的时刻。想象一下两个人在打长途电话,一个在美国西海岸,一个在突尼斯突尼斯。

捕捉他们通话开始的时刻。

Instant callStartedUtc = Instant.now() ;

当美国人一边打电话一边看墙上的时钟时,他们看到了什么时间?

ZonedDateTime zdtLosAngeles = callStartedUtc.atZone( ZoneId.of( "America/Los_Angeles" ) ) ;

当突尼斯人拿起电话接听时,瞥了一眼挂在墙上的时钟,他们看到的是几点?

ZonedDateTime zdtTunis = callStartedUtc.atZone( ZoneId.of( "Africa/Tunis" ) ) ;

三个对象都代表同一个时刻。您可以从两个ZonedDateTime 对象中提取Instant 对象。

Instant instant = zdtLosAngeles.toInstant() ;

这些都是平等的。在此示例代码中,eq 将是 true

boolean eq = 
    callStartedUtc.equals( zdtLosAngeles.toInstant() ) 
    &&
    callStartedUtc.equals( zdtTunis.toInstant() )
;

所有三个内部都具有与纪元参考相同的计数。

UTC 作为 真正的时间

提示:在工作编程或做系统管理员工作时,学会用 UTC 来思考。 将 UTC 视为一个真实时间,时区只是变化。

不要在脑海中来回翻译,只要坚持使用 UTC。就像学习一门外语一样,脑子里不断的翻译会让你发疯。

在程序或系统之间交换日期时间值、日志记录、调试等都应该在 UTC 中完成。我建议将你桌上的第二个时钟设置为 UTC。

LocalDateTime 不是片刻

你说:

//我喜欢用LocalDateTime,所以我把Date转换成LocalDateTime

LocalDateTime ldtOfSystem = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());

你不应该“喜欢”LocalDateTime。那个类不能代表一个时刻。它包含日期和时间,但缺少时区或偏离 UTC 的上下文。所以它可能会说“2021 年 1 月 23 日中午”,但我们不知道这是否意味着日本东京的中午、法国图卢兹的中午或美国俄亥俄州托莱多的中午——所有这些时刻都非常不同,相隔几个小时。

当您跟踪某个时刻,时间轴上的特定点时,您不能使用LocalDateTime。而是使用Instant(用于UTC)、OffsetDateTime(用于与UTC 的偏移量,一个数字小时-分钟-秒)或ZonedDateTime(用于时区,在Continent/Region 名称中,例如@ 987654365@).

如有疑问,请不要使用LocalDateTime。我发现大部分时间在商业应用程序中,我们都在跟踪一个特定的时刻。最大的例外是在预订未来的活动(例如约会)时,需要LocalDateTime

搜索以了解更多信息,因为这已在 Stack Overflow 上多次介绍。例如,What's the difference between Instant and LocalDateTime?

时间服务器

你说:

澄清。为什么我需要 UTC 时间?

我假设您真的想说“为什么我需要从受信任的远程时间服务器获取当前时刻?”。如果是这样,我要求您编辑您的问题以清楚起见。

这种需求是合法的。如果本地时钟不可信,并且您的值必须接近当前时刻,那么您确实应该联系受信任的时间服务器(如果有的话)。

UTC 这个词在这里并不重要。时间服务器最有可能返回 UTC 值。但是在UTC与本地或远程时间的来源无关。

GMT 与 UTC

你说:

我需要将这个 GMT 日期转换为 UTC

不,几乎可以肯定的是。

对于几乎所有程序员来说,GMT 和各种 UTC 的确切定义冗长、复杂、学术和没有实际意义。

对于会计和工作流程等常规业务应用程序,您可以将 GMT 和UTC 视为同义词。差异实际上不到一秒钟。 Java 使用的默认时钟确实忽略了这种差异。

除非您使用火箭遥测、GPS/伽利略卫星或原子钟,否则您真的不应该关心 GMT 与 UTC。

此外,在您使用远程时间服务器的情况下,GMT 与 UTC 更不适用,因为检索到的值在其通过网络的遍历过程中会损失大量时间。

【讨论】:

  • 巴兹尔,你的答案是真正的宝石。非常感谢您抽出宝贵时间以透彻且易于理解的方式分享您的知识。至于我在澄清中提出的问题,这是一种告诉读者我为什么需要UTC时间的目的的方式,我在随后的句子中解释了(或试图解释并失败了)。
  • 当您将zdtLosAngeleszdtTunis 的瞬间与第三个instant 进行比较时,您是从zdtLosAngeles 获取的。如果我错了,请纠正我,我认为您真正想与它们进行比较的是Instant callStartedUtc = Instant.now() ;
  • @MirmuhsinSodiqov 是的,我的意思是使用callStartedUtc。已修复,谢谢。
猜你喜欢
  • 2020-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-03-06
  • 1970-01-01
  • 2019-05-09
  • 1970-01-01
相关资源
最近更新 更多