ZonedDateTime.toInstant().toEpochMilli() 丢失区域数据

ZonedDateTime.toInstant().toEpochMilli() losing zone data

我正在使用系统时区 returns 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]

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

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

澄清。为什么我需要 UTC 时间? 我无法控制用户的时间。 He/she 可以轻松更改 his/her 设备的日期和时间,这给我们带来了问题。因此,为了解决这个问题,我们找到了一个名为 TrueTime 的库,它提供实时 Date 对象,如下所示:

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

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

你好像误会了一两件事。

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

A Date 没有任何时区或与 UTC 或 GMT 的偏移量。所以它不能在格林威治标准时间。一个Date就是一个时间点,不多也不少。顺便说一句,我通常建议您根本不要使用 class,因为它设计不佳且早已过时;但是如果 TrueTime 还不能给你一个 Instant 或其他现代的 class,我们现在必须使用 Date

I am losing zone data and it is returning milliseconds based on the local zone (system zone). …

自纪元以来的毫秒计数与时区无关。纪元通常以UTC定义,但它是一个时间点。因此毫秒从不基于任何时区(或者你可以说它们总是基于UTC,这取决于你喜欢如何使用这些词,意思是一样的)。

… I am converting these milliseconds to Date again and the result is in GMT+3 [2020-07-13T18:00:57.192+03:00]

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

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

getTrueNowTimeInGMT().getTime();

由于我上面所说的,无论设备的时区设置如何,这都是可靠的。

正确。我会添加一些想法和图表。

从不使用 java.util.Date

您的代码:

Date now = getTrueNowTimeInGMT();

查看您的图书馆是否已更新 java.timejava.util.Date class 多年前被 JSR 310 取代。应使用 java.time.Instant class。

Instant 表示 UTC 中的时刻

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

Instant instant = myJavaUtilDate.toInstant() ;

你说:

Am I missing something? I read in one post that toInstant() method doesn't care about the timezone.

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

  • 通过Instant.now捕获当前时刻将始终以UTC视角显示。
  • Adding/subtracting 通过 Instant::plusInstant::minus 的时间跨度将始终采用 UTC,并且涉及不遇到政治时间异常,例如 Daylight Saving Time (DST).

从纪元参考开始计数

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

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

long millisSinceEpoch = instant.toEpochMilli() ;

然后又回来了。

Instant instant = Instant.ofEpochMilli( millisSinceEpoch ) ;

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

String output = instant.toString() ;

然后又回来了。

Instant instant = Instant.parse( input ) ;

ZonedDateTime

你说:

How can I get the milliseconds from the specific time zone I pointed out in ZonedDateTime?

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

捕捉他们通话开始的瞬间。

Instant callStartedUtc = Instant.now() ;

当美国人在拨打 phone 时瞥了一眼墙上的时钟,他们看到的是什么时间?

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

当突尼斯人拿起 phone 回答时瞥了一眼挂在墙上的时钟,他们看到的是什么时间?

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不是片刻

你说:

// I like to use LocalDateTime, so I am converting Date to LocalDateTime:

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

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

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

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

搜索以了解更多信息,因为这已在 Stack Overflow 上多次介绍。例如,.

时间服务器

你说:

Clarification. Why do I have the need for the UTC time?

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

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

术语 UTC 在这里并不相关。时间服务器最有可能 return 一个 UTC 值。但是使用 UTC 与时间来源是本地还是远程无关。

GMT 与 UTC

你说:

I need to convert this Date which is GMT to UTC

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

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

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

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

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