来自 ZonedDateTime UTC 实例的 Java 日期和时间戳

Posted

技术标签:

【中文标题】来自 ZonedDateTime UTC 实例的 Java 日期和时间戳【英文标题】:Java Date and Timestamp from instance of ZonedDateTime UTC 【发布时间】:2017-09-01 18:03:10 【问题描述】:

我有一个 java 应用程序,我想在其中使用 UTC 时间。目前,代码混合使用java.util.Datejava.sql.Timestamp。为了获得UTC时间,我之前的程序员使用:

日期:

 Date.from(ZonedDateTime.now(ZoneOffset.UTC)).toInstant();

对于时间戳

 Timestamp.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());

但是,我自己使用此代码运行了多个测试,并且这两行都返回当前日期/时间(在我当前的时区中)。从我读过的所有内容中看来 Date/Timestamp 没有 zoneOffset 值,但我找不到具体的说明。

无论如何要在 Date 或 Timestamp 对象中保留 timeZone (UTC),还是我需要进行一些重构并在整个应用程序中使用实际的 ZonedDateTime 对象?这个 ZonedDateTime 对象还会与当前的 sql 时间戳对象兼容吗?

例子:

public static void main (String args[])

    ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneOffset.UTC);
    Timestamp timestamp = Timestamp.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
    Date date = Date.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
    System.out.println("ZonedDateTime: " + zonedDateTime);
    System.out.println("Timestamp: " + timestamp);
    System.out.println("Date: " + date);

输出:

 ZonedDateTime: 2017-04-06T15:46:33.099Z
 Timestamp: 2017-04-06 10:46:33.109
 Date: Thu Apr 06 10:46:33 CDT 2017

【问题讨论】:

"...返回当前日期/时间(在我当前的时区中)...",也许您刚刚在java.util.Date 上使用了toString() 的方法来得出结论,但是这个方法只使用外部默认时区来打印自己。偏移量或时区绝不是java.util.Date 的一部分,其真实状态由getTime() 方法反映。所以请澄清你真正做了什么。 日期和时间戳都以格林威治标准时间存储时间。切换时区只会改变显示值。 Date.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant())new Date() 之间没有逻辑上的区别。除了前者更令人困惑。 通常我不建议查看源代码,因为一个类的行为是由它的契约 (javadoc) 定义的,但是从 Java 8 开始,the source of java.util.Date 表明它只有两个实例字段:一个 long值和仅由现已弃用的方法使用的“BaseCalendar”对象(可以追溯到 Calendar 类不存在并且 Date 类试图提供所有与日历相关的功能时)。它只是包装了一个毫秒值;它不保留时区信息。 【参考方案1】:

tl;博士

Instant.now()  // Capture the current moment in UTC with a resolution up to nanoseconds.

仅使用 java.time 类。避免在 Java 8 之前添加的麻烦的旧旧日期时间类。

使用 java.time

您之前的程序员正在使用新的现代 java.time 类,这些类现在取代了臭名昭著的老旧日期时间类,例如 DateCalendarTimestamp

Instant

Instant 类表示UTC 中时间轴上的时刻,分辨率为nanoseconds(最多九 (9) 位小数)。获取 UTC 中的当前时刻非常简单:Instant.now

Instant instant = Instant.now();

转换

您应该坚持使用 java.time 类,并避免使用遗留类。但是,如果绝对必要,例如与尚未为 java.time 更新的旧代码交互,您可以转换为 java.time 或从 java.time 转换。寻找旧类的新方法。旧类java.util.Date 等效为Instant

java.util.Date d = java.util.Date.from( myInstant); // To legacy from modern.
Instant instant = myJavaUtilDate.toInstant();  // To modern from legacy.

JDBC

避免使用旧的日期时间类。请改用 java.time 类。

您的JDBC 4.2 兼容driver 可以通过调用PreparedStatement::setObjectResultSet::getObject 直接寻址java.time 类型。

myPreparedStatement.setObject( … , instant ) ;

……和……

Instant instant = myResultSet.getObject( … , Instant.class ) ;

如果不是,则回退到使用 java.sql 类型,但要尽可能简短。使用添加到旧类的新转换方法。

myPreparedStatement.setTimestamp( … , java.sql.Timestamp.from( instant ) ) ;

……和……

Instant instant = myResultSet.getTimestamp( … ).toInstant() ;

不需要ZonedDateTime

请注意,我们不需要您提到的 ZonedDateTime,因为您说您只对 UTC 感兴趣。 Instant 对象始终采用 UTC。这意味着您引用的原始代码:

Date.from(ZonedDateTime.now(ZoneOffset.UTC)).toInstant();

...可以简单地缩短为:

Date.from( Instant.now() ) ;

请注意,java.util.Date 也始终采用 UTC。然而,不幸的是,它的toString 在生成字符串时隐式地应用了 JVM 的当前默认时区。正如您在 Stack Overflow 上搜索看到的那样,此反功能不会造成混乱。

如果您想通过区域wall-clock time 的镜头查看您的Instant 对象的UTC 值,请指定时区ZoneId 以获取ZoneDateTime

continent/region 的格式指定proper time zone name,例如America/MontrealAfrica/CasablancaPacific/Auckland。切勿使用 3-4 个字母的缩写,例如 CDTESTIST,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。

ZoneId z = ZoneId.of( "America/Chicago" );
ZonedDateTime zdt = instant.atZone( z );


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310。

您可以直接与您的数据库交换 java.time 对象。使用符合JDBC 4.2 或更高版本的JDBC driver。不需要字符串,不需要java.sql.* 类。

从哪里获得 java.time 类?

Java SE 8Java SE 9Java SE 10Java SE 11 和更高版本 - 具有捆绑实现的标准 Java API 的一部分。 Java 9 添加了一些小功能和修复。 Java SE 6Java SE 7 ThreeTen-Backport 中的大部分 java.time 功能都向后移植到 Java 6 和 7。 Android java.time 类的 android 捆绑包实现的更高版本。 对于早期的 Android (ThreeTenABP 项目适应 ThreeTen-Backport(如上所述)。见How to use ThreeTenABP…

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuarter 和more。

【讨论】:

为什么建议使用setObject() 而不是使用java.sql 类型timestamp @mFeinstein 因为Timestamp 是糟糕的类设计的可憎之物。 java.time 框架由 Sun、Oracle 和 JCP 设计和采用,以完全取代旧的日期时间类。 JDBC 4.2 更新的要点之一是向现代 java.time 类的过渡。 setObject 对此有何表现?它会将我的 ZonedDateTime 保存为 blob 吗?我能否对保存在数据库中的数据进行查询?它是否适用于所有java.time 值作为InstantPeriod 我目前正在使用来自InstantTimestamp 并且我的mysql 数据库中的所有TIMESTAMP 数据都是UTC 格式,我可以轻松搜索并阅读它,那我失去了什么? @mFeinstein 从 JDBC 4.2 开始,不再需要再次触及 java.sql.Timestamp 类,也不需要在 java.time 包之外的任何其他与日期时间相关的类。我是 Postgres 类型的人,但显然 MySQL 中的 TIMESTAMP 数据类型代表 UTC 中的一个时刻,并且类似于 SQL 标准类型 TIMESTAMP WITH TIME ZONE。因此,您应该使用java.time.Instant 与该类型的列交换值。如果您有一个ZonedDateTime 对象,只需调用它的toInstant 方法即可通过您的JDBC 驱动程序与您的数据库进行对话。取回后拨打Instant::atZone【参考方案2】:

在 Java 中,Date 代表一个时间点。它与时间戳无关。当您调用 Date 对象的 toString() 方法时,它会将该时间转换为平台的默认时间戳,例如以下将以 UTC 格式打印日期/时间(因为它将默认时区设置为 UTC):

TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
ZonedDateTime zonedDateTime = ZonedDateTime.now(ZoneOffset.UTC);
Timestamp timestamp = Timestamp.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
Date date = Date.from(ZonedDateTime.now(ZoneOffset.UTC).toInstant());
System.out.println("ZonedDateTime: " + zonedDateTime);
System.out.println("Timestamp: " + timestamp);
System.out.println("Date: " + date);

【讨论】:

以上是关于来自 ZonedDateTime UTC 实例的 Java 日期和时间戳的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 ZonedDateTime 或 Java 8 将任何日期时间转换为 UTC

OffsetDateTime 到 ZonedDateTime - 带有特定的 ZoneId

如何在春季禁用将 ZonedDateTime 字段的时区转换为 UTC,仅用于一个字段并将其保留用于所有其他字段?

巴西的 ZonedDateTime 仍然得到 -2 而不是 -3 偏移量

获取当前时间UTC时间的下一个15分钟时间点

在时区将 ZonedDateTime 转换为 LocalDateTime