在 Java 中获取 UTC 时间戳

Getting the UTC timestamp in Java

一个old Stack Overflow posting建议在Java中获取UTC时间戳的方法如下:

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

不幸的是,这对我不起作用。我有一个非常简单的程序(转载如下),它演示了不同的行为。

On Windows:时间为本地时间,标有与GMT

的偏移量

On 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

您的代码:

Date.from(Instant.now())

您将糟糕的遗留 classes 与它们的替代品,即现代 java.time classes.

混合在一起

不要。

永远不要使用 Date。当然不需要混用 java.time.Instant.

为了解释您的特定示例,请理解在 Date class 中,许多糟糕的设计选择是其 Date#toString 方法隐式应用的反特征JVM 在生成其文本时的当前默认时区

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

Sun、Oracle 和 JCP 放弃了遗留日期时间 classes。我们都应该如此。我建议您不要花时间尝试理解 DateCalendarSimpleDateFormat 等。

你问过:

Question: How do we display the UTC timestamp in a Java program?

Instant.now().toString()

看到 code run live at IdeOne.com.

2021-01-22T21:50:18.887335Z

你说:

On Windows: …

On Linux: …

在 Windows、Linux、BSD、macOS、iOS、Android、AIX、等等。


这是我制作的 table 以指导您从遗留 classes.运行sitioning。

java.util.Date 对象不是像 modern date-time types 这样的真正的日期时间对象;相反,它表示自称为“纪元”的标准基准时间以来的毫秒数,即 January 1, 1970, 00:00:00 GMT(或 UTC)。当您打印 java.util.Date 的对象时,其 toString 方法 returns JVM 时区中的日期时间,根据该毫秒值计算得出。如果您需要在不同的时区打印日期时间,您需要将时区设置为 SimpleDateFormat 并从中获取格式化字符串。

我建议您只使用 Instant.now(),您可以将其转换为其他 java.time 类型。

java.util 的日期时间 API 及其格式 API、SimpleDateFormat 已过时且容易出错。建议完全停止使用它们并切换到 modern date-time API.

  • 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它向后移植了大部分 java.time Java 6 和 7 的功能。
  • 如果您正在为 Android 项目工作,并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaring and

但是,如果您仍想使用 java.util.Date,请按上述方法使用 SimpleDateFormat

演示:

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

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

如果您想输出瞬间,现在最简单的方法是使用 OffsetDateTime,这样您就可以选择在您想要的时区显示瞬间 - 在您的情况下为 UTC。使用 OffsetDateTime.now()OffsetDateTime.ofInstant(),但如果您在应用程序逻辑中使用即时,则坚持使用即时。

suggests that the way to get the UTC timestamp in Java is the following:

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

这个问题以及该话题中的大多数答案具有误导性

Instant 代表一个瞬间。现在是 'solarflares' 时间:绝对没有一点代表人类大脑发明的任何东西,而 UTC 是一个时区:人类的发明。宇宙、太阳、天文学——他们不知道 UTC 是什么,也不关心——这就是 Instant 的全部意义所在。瞬间没有'hours'或'days'或'timezones'等人类概念。立即询问它发生在哪一天是没有意义的。它不能告诉你;某些事件发生了:如果我问一个 19 世纪的俄罗斯人,那件事是什么时候发生的,他们可能会给出完全不同的答案,例如,如果我问一个住在仅向西 100 英里的人。 Instant 不知道要应用哪种本地化,因此不会让你问这个问题——这是一件好事,对象不应该公开方法,它给出的任何答案要么是官话,要么至少需要了解各种令人惊讶的事情注意事项。

至关重要的是,如果您告诉我“... 在 UTC 中”,您肯定可以准确地知道是哪一个月、哪一天等等。 而 Instant 不会这样做,这就是为什么说 java.time.Instant 代表 UTC 时间的时刻会产生误导。 没有。它代表一个时刻(不在任何特定时区)。

是的,在 Instant 内部,就像 Date 一样,只是对 System.currentTimeMillis() returns: "millis since epoch" 的简单包装,但了解它的关键是 'UTC' 不是它 的意思 的一部分,因此,当您将 Instant 实例提供给其他一些方法(例如 System.out.println,通过 JDBC, 等), 该方法绝对没有义务假设 UTC 在语义上是相关的。

当你想将人类的计时概念(年、日、月、小时、分钟、毫秒,是的,时区)与或多或少绝对*时间的概念混合时,正确的答案是java.time.ZonedDateTime。请注意,根据定义,任何不基于 java.time.* 的时间表示都是错误的,就像在大多数编程语言中一样 - 事实证明,时间比大多数采用库来表示它所实现的要复杂得多。 java.time 实际上是第 4 次尝试编写时间库这一事实应该充分表明很难做到正确。

ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);

是你想要的 - 这不仅仅是你想要的实现细节,而是准确描述你 意思的代码:现在,在 UTC 时区,存储在一个对象中,该对象在语义上不仅存储正确的时间,而且还存储并紧紧地纠缠在它的身份中,它特别是在 UTC 而不是被重新解释,移动到本地区域,或任何其他此类恶作剧 - 至少,除非您明确要求它这样做。

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

请注意,日期是旧的 API,因此必然会损坏。在这种情况下,Date 是说谎的骗子——它不代表日期,它代表瞬间;它的名字不好。 (第二个 API 是 Calendar,也坏了。例如,这也是一个撒谎的骗子:它根本不代表 Calendar。它代表了分区日期时间和瞬间的一些奇怪的合并,并且很合适不代表任何结果)。任何时候你去日期 API 怪异接踵而至,像 'I just want the concept of the time, at some specific moment, in UTC' 这样简单的事情在这些 API 中是不可能的。你现在依赖于链上下游所有各种库几乎没有定义的行为 - 你实际上陷入了祈祷他们做正确的事情,或者钻研异国情调的设置试图哄骗这些库做你想做的事。

TL;DR: 使用 java.time.

*) 请注意,ZonedDateTime 是 而非 绝对值。例如,如果你有时间 January 20th, 2023, 8 in the morning, at Europe/Amsterdam,以 ZonedDateTime 对象的形式,那么从现在到那一刻之间经过的秒数肯定看起来不会改变,也不会改变当例如由于夏令时,阿姆斯特丹经历了一个小时的变化。但是,如果荷兰政府颁布法令,从今以后荷兰将不再移动时钟并将永远保持夏令时(可能 - 欧盟指令已经到位,现在只是时间问题),然后在木槌落地的那一刻,您的约会恰好转移了 1 小时。

这有望提供对差异的重要洞察:即时,代表事件(因此我喜欢称它为 'solarflares time',以尽可能将其从人类时间保持概念中解脱出来),甚至不理解这种决定对事物产生影响的概念。另一方面,ZonedDateTime 本质上与它相关 - 因此 ZonedDateTime.

中的 Zone

如果您想存储理发师预约并使用 Instant 来完成,您 迟到或早到一个小时。

一个Instant object and also a Date对象本身 仅包含时间点,但不包含时区信息。 此外,Date class 的 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)。

有时您的程序必须使用较旧的 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]

简单可行的方法!

我的要求是带毫秒的日期时间

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"));
    }