来自 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.Date
和java.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 类,这些类现在取代了臭名昭著的老旧日期时间类,例如 Date
、Calendar
、Timestamp
。
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::setObject
和ResultSet::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/Montreal
、Africa/Casablanca
或Pacific/Auckland
。切勿使用 3-4 个字母的缩写,例如 CDT
或 EST
或 IST
,因为它们不是真正的时区,没有标准化,甚至不是唯一的 (!)。
ZoneId z = ZoneId.of( "America/Chicago" );
ZonedDateTime zdt = instant.atZone( z );
关于java.time
java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.Date
、Calendar
和 SimpleDateFormat
。
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 8、Java SE 9、Java SE 10、Java SE 11 和更高版本 - 具有捆绑实现的标准 Java API 的一部分。 Java 9 添加了一些小功能和修复。 Java SE 6 和 Java 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 的试验场。您可能会在这里找到一些有用的类,例如Interval
、YearWeek
、YearQuarter
和more。
【讨论】:
为什么建议使用setObject()
而不是使用java.sql
类型timestamp
?
@mFeinstein 因为Timestamp
是糟糕的类设计的可憎之物。 java.time 框架由 Sun、Oracle 和 JCP 设计和采用,以完全取代旧的日期时间类。 JDBC 4.2 更新的要点之一是向现代 java.time 类的过渡。
setObject
对此有何表现?它会将我的 ZonedDateTime
保存为 blob 吗?我能否对保存在数据库中的数据进行查询?它是否适用于所有java.time
值作为Instant
和Period
?
我目前正在使用来自Instant
的Timestamp
并且我的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,仅用于一个字段并将其保留用于所有其他字段?