首页 > 解决方案 > ZonedDateTime.toInstant().toEpochMilli() 丢失区域数据

问题描述

我正在使用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 并将其转换为毫秒。

标签: androidjava-8java-timejava.time.instant

解决方案


Ole VV的答案是正确的。我将添加一些想法和图表。

从不使用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::plus并且Instant::minus将始终使用 UTC,并且涉及通用的 24 小时 UTC 时间,不会遇到诸如夏令时 (DST)之类的政治时间异常。

从纪元参考计数

这两个类,传统的和现代的,代表了 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 ) ;

Java 中所有日期时间类型的表,包括现代和传统

ZonedDateTime

你说:

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

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

捕捉他们通话开始的那一刻。

Instant callStartedUtc = Instant.now() ;

当美国人一边打电话一边看墙上的时钟时,他们看到的是几点?

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

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

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

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

Instant instant = zdtLosAngeles.toInstant() ;

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

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

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

UTC 作为唯一的真实时间

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

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

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

LocalDateTime不是片刻

你说:

// 我喜欢使用LocalDateTime,所以我正在转换DateLocalDateTime

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

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

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

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

搜索以了解更多信息,因为这已在 Stack Overflow 上多次介绍。例如,Instant 和 LocalDateTime 有什么区别?.

时间服务器

你说:

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

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

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

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

GMT 与 UTC

你说:

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

不,你没有,几乎可以肯定。

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

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

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

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


推荐阅读