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

时间:2020-07-13 15:22:31

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

我正在使用TrueTime库,该库在系统时区返回Date。转换为毫秒时,我无法将此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(格林尼治标准时间)转换为UTC并将其转换为毫秒。

2 个答案:

答案 0 :(得分:2)

您似乎误解了一两件事。

// 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来确定,这取决于您更喜欢使用这些词的含义)。

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

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

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

getTrueNowTimeInGMT().getTime();

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

答案 1 :(得分: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中的某个时刻。

  • 始终会从UTC的角度看到通过Instant.now捕获当前时刻。
  • 通过Instant::plusInstant::minus添加/减去时间范围将始终是UTC,并且涉及UTC时间的24小时通用天,而不会遇到政治时间异常例如Daylight Saving Time (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 ) ;

Table of all date-time types in Java, both modern and legacy

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作为一个真实的时间

提示:在工作编程或进行sysadmin时,学习如何考虑UTC。 将UTC视为一个真实时间,只是时区不同。

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

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

LocalDateTime不是片刻

您说:

//我喜欢使用LocalDateTime,所以我将Date转换为LocalDateTime

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

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

无论何时跟踪时刻(时间轴上的特定点),都不能使用LocalDateTime。而是在{{{ 1}}名称,例如Instant

如有疑问,请不要使用OffsetDateTime。我发现大部分时间都在商务应用中,我们正在跟踪特定的时刻。最大的例外是在预订将来的事件(例如约会)时需要ZonedDateTime

搜索以了解更多信息,因为Stack Overflow已经对此进行了很多次讨论。例如What's the difference between Instant and LocalDateTime?

时间服务器

您说:

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

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

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

Continent/Region一词在这里并不重要。时间服务器最有可能返回UTC值。但是进入UTC与本地时间或远程时间无关。

格林尼治标准时间与UTC

您说:

我需要将此格林威治标准时间转换为UTC

不,您不会,几乎可以肯定。

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

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

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

此外,在使用远程时间服务器的情况下,GMT与UTC的适用性甚至更低,因为检索到的值在通过网络进行遍历时会浪费大量时间。