获取UTC时间戳

6

一篇旧的Stack Overflow帖子建议在Java中获取UTC时间戳的方法如下:

Instant.now()   // Capture the current moment in UTC.

很遗憾,这对我不起作用。下面是一个非常简单的程序(复制如下),它展示了不同的行为。

在Windows上:时间是本地时间,并带有与GMT的偏移量标签

在Linux上:时间再次是本地时间,并且为本地时区正确标记


问题:如何在Java程序中显示UTC时间戳?

我的示例源代码如下:

import java.time.Instant;
import java.util.Date;

public class UTCTimeDisplayer {
    public static void main(String[] args) {
        System.out.println(System.getProperty("os.name"));
        Date currentUtcTime = Date.from(Instant.now());
        System.out.println("Current UTC time is " + currentUtcTime);
    }
}  

Windows 输出:

C:\tmp>java UTCTimeDisplayer
Windows 10
Current UTC time is Fri Jan 22 14:28:59 GMT-06:00 2021

Linux 输出:

/tmp> java UTCTimeDisplayer
Linux
Current UTC time is Fri Jan 22 14:31:10 MST 2021

当您执行 System.out.println(Instant.now().toString()) 时会发生什么? - Eugene
@Eugene 它输出相同。 - Sandeep
1
有些可疑... MSTGMT-7 (UTC-7),而不是 GMT-6 - Turing85
3
我猜您想要的是 System.out.println(Instant.now().atOffset(ZoneOffset.UTC)) - Eugene
或者你想要这个吗? System.out.println(Instant.now().atZone(ZoneId.of("UTC"))); - matt
这个回答解决了您的问题吗?Java中的时区问题...这个这个这个这个? - Ole V.V.
7个回答

13
你的代码:
Date.from(Instant.now())

你正在混合使用过时的遗留类和它们的替代品,即现代的 java.time 类。

不要这样做。

绝对不要使用 Date。当然也不需要与 java.time.Instant 混合使用。

为了解释你的特定示例,请理解 Date 类的许多糟糕设计选择之一是其 Date#toString 方法在生成文本时隐式应用 JVM 的当前默认时区 这一反功能。

你在两个具有不同当前默认时区的 JVM 上运行了你的代码,因此得到了不同的输出。

Sun、Oracle 和 JCP 放弃了遗留的日期时间类。我们所有人都应该这样做。我建议你不要浪费时间去尝试理解 DateCalendarSimpleDateFormat 等。

你问道:

问题:如何在 Java 程序中显示 UTC 时间戳?

Instant.now().toString()

请看这份代码在 IdeOne.com 上的实时运行结果。

2021-01-22T21:50:18.887335Z

你说:

在Windows上:...

在Linux上:...

无论是在 Windows、Linux、BSD、macOS、iOS、Android、AIX 等操作系统中,使用 Instant.now().toString() 可以获得相同一致的结果。


下面是我制作的一个表格,可以帮助您过渡到新的类。

enter image description here


7
java.util.Date对象不是像现代日期时间类型一样的真实日期时间对象;相反,它表示自标准基准时间“时代”以来的毫秒数,即1970年1月1日00:00:00 GMT(或UTC)。当你打印一个java.util.Date对象时,它的toString方法会返回JVM时区中从这个毫秒值计算而来的日期时间。如果您需要在不同的时区打印日期时间,则需要设置SimpleDateFormat的时区并从中获取格式化字符串。
我建议您直接使用Instant.now(),您可以将其转换为其他的java.time类型。 java.util的日期时间API及其格式化API SimpleDateFormat已经过时且容易出错。建议完全停止使用它们,并切换到现代日期时间API 然而,如果您仍然想使用java.util.Date,请像上面提到的那样使用SimpleDateFormatDemo:
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.util.Date;
import java.util.TimeZone;

public class Main {
    public static void main(String[] args) {
        Date currentUtcTime = Date.from(Instant.now());
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
        sdf.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
        System.out.println("Current UTC time is " + sdf.format(currentUtcTime));
    }
}

输出:

Current UTC time is 2021-01-22 21:53:07 UTC

3
在Java中获取UTC时间戳的方法是使用以下代码:Instant.now() //捕获当前时刻的UTC。但这种说法以及此线程中的大多数回答都是误导性的。 Instant代表时间上的一个瞬间,它是“太阳耀斑”的时间,绝对不能代表任何人类大脑发明的东西,而UTC是一个时区,是人类的发明。宇宙、太阳、天文学——它们不知道UTC是什么,也不关心——这就是Instant的全部内容。瞬间缺乏诸如“小时”、“天数”或“时区”等人类概念。询问瞬间发生的日期毫无意义。它无法告诉你;发生了某些事件:如果我问19世纪的俄罗斯人那是哪一天,他们可能会给出完全不同的答案,而如果我问住在离这里仅有100英里的地方的人,答案也会不同。Instant不知道要应用哪种本地化,因此不允许您向其提出这个问题——这是一件好事,对象不应该暴露任何答案都是胡言乱语或至少需要了解各种令人惊讶的警告的方法。如果你告诉我“...在UTC”,你肯定可以精确地告诉我是哪个月、哪一天等。Instant不会做到这一点,这就是为什么说java.time.Instant代表UTC时间的瞬间是误导性的原因。它不是这样。它表示一个时间点(不属于任何特定时区)。当你想要将时间感知(年、日、月、小时、分钟、毫秒和时区)与更或多或少绝对的时间概念混合在一起时,正确的答案是java.time.ZonedDateTime。请注意,使用不基于java.time.*的任何时间表示都是有缺陷的,因为在大多数编程语言中,时间比大多数库所能表示的还要复杂。实际上,java.time是第四次尝试编写时间库的产物,这应该足以说明它很难弄对。
ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
< p > 就是你想要的——这不仅是实现细节上你想要的,而且它恰好描述了你的意思:目前,在UTC时区存储在一个语义上不仅存储正确时间,而且还紧密纠缠于其身份的对象中,该对象特定地位于UTC并且不会被重新解释,移动到本地区域或进行任何其他类似的把戏——至少在你明确要求它这样做之前不会这样。

< blockquote >

Date currentUtcTime = Date.from(Instant.now());

请注意,Date是旧的API,因此必然存在问题。 在这种情况下,Date是撒谎的骗子-它不代表日期,而代表瞬间; 它的命名不好。 (第二个API是日历,也是有问题的。例如,那也是撒谎的骗子:它根本不代表日历。它代表一些奇怪的带时区的日期时间和瞬间的混合物,并且由于后果不适合表示任何一个)。每次使用Date API都会出现奇怪的情况,而像“我只想要某个特定时刻在UTC中的时间概念”这样简单的事情在这些API中是不可能的。您现在依赖于所有各种库链上传下传递的几乎未定义的行为-实际上,您被困在祈祷它们做正确事情或深入研究来让这些库做你想要的事情。

TL; DR:使用 java.time

*)请注意,ZonedDateTime不是绝对的。例如,如果您有以欧洲/阿姆斯特丹为例的时间 2023年1月20日早上8点,以 ZonedDateTime 对象的形式,则现在和那个时刻之间将过去的秒数似乎没有变化并且不会更改,例如阿姆斯特丹进行夏令时调整。但是,如果荷兰政府命令从此不再移动时钟,并永远停留在夏令时(这很可能 - 欧盟指令已经到位,现在只是时间问题),则在榔头落地的那一刻,您的约会将准确地提前或拖后1小时。

希望这能提供关键的见解:表示事件的瞬间(因此我喜欢称其为“太阳耀斑时间”,尽可能地将其与人类时间概念分离),甚至不理解此类决定对事物产生影响的概念。另一方面,ZonedDateTime则固有地被绑定在其中-因此是 Zone 中的 ZonedDateTime

如果您想存储理发预约并使用 Instant 来执行此操作,则很快就会迟到或提前一小时。


很好的全面讨论。我的理解是,UTC不幸地介入了,因为瞬间是相对于Unix纪元来测量的,而纪元并没有与UTC或任何时区、日历或任何人类命名相关联,它只是一个时间点,事实上,它只能用通俗易懂的人类术语来描述,并且需要一个日期标签和该标签的位置,因此1970年1月1日UTC午夜与其他任何时间一样合理,但这是最明智的选择方式来描述它。 - Chris
虽然我认为你在以“是的,内部…”开头的段落中说了同样的话。 - Chris

2

一个简单的方法可以解决问题!

我的需求是带毫秒的日期时间。

2021-11-25 19:55:00.743

private String getUTCTimestamp() {
        ZonedDateTime utc = ZonedDateTime.now(ZoneOffset.UTC);
        return utc.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
    }

2
一个 Instant 对象和一个 Date 对象本身只包含一个时间点,但没有时区信息。此外,Date 类的 toString() 方法隐式地选择系统环境提供的时区,这不是你想要的。
因此,你需要显式地选择时区(在你的情况下是 UTC)。例如像这样:
Instant instant = Instant.now();
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.UTC);
System.out.println("Current UTC time is " + offsetDateTime);

这将独立于操作系统打印。
Current UTC time is 2021-01-22T22:37:21.950354100Z

结尾的Z表示零时区偏移量(即UTC)。


1

Instant.now() 实际上是自纪元(UTC中的1970年1月1日午夜)以来的时间段,但您正在使用Date来表示该时刻。Date反映了具有毫秒精度的时刻,但正如https://docs.oracle.com/javase/8/docs/api/java/util/Date.html中所解释的那样,应该使用Calendar来呈现日期,因为Date的呈现取决于主机。实质上,Date包装了时刻,但根据其他因素显示。

如果您想输出即时时间的最简单方法是使用OffsetDateTime,以便您可以选择在所需的时区(在这种情况下为UTC)中呈现即时时间。使用OffsetDateTime.now()OffsetDateTime.ofInstant(),但如果您在应用程序逻辑中使用即时,则只需坚持使用Instant。


0
有时候你的程序需要与旧版本的Java一起工作,所以这里提供一个1.5版本的示例:
    java.text.SimpleDateFormat tfGMT = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    java.util.Calendar cUTC = java.util.Calendar.getInstance (java.util.TimeZone.getTimeZone ("GMT+0"));
    tfGMT.setCalendar (cUTC);
    java.util.Date d= new java.util.Date ();
    String s= tfGMT.format (d);
    System.out.printf ("now=%s [unix ts=%d.%03d]\n", s, d.getTime()/1000, d.getTime()%1000);

请注意,前三行不必在每次调用时重复,但请记住SimpleDateFormat不是线程安全的。(简单解决方案:为每个线程创建一个。)
示例用法(它显示设置TZ不会影响UTC时间戳):
$ TZ=GMT+3 java5 now_utc; TZ=GMT-3 java5 now_utc
now=2021-01-24 12:56:14 [unix ts=1611492974.264]
now=2021-01-24 12:56:14 [unix ts=1611492974.726]

对于Java 1.5,我建议(1)升级到更新的Java版本(2)如果短期内更改Java版本不可行,则使用Joda-Time处理日期和时间。 - Ole V.V.
是的,那些是非常好的建议。另外你也可以直接使用我写的解决方案,它可以在不升级或使用外部组件的情况下运行。 - Lorinczy Zsigmond

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接