如何将 ZonedDateTime 转换为 Date?

我试图在我的数据库中设置一个服务器不可知的日期时间,我相信这样做的最佳实践是设置一个 UTC DateTime。我的 db 服务器是 Cassandra,Java 的 db 驱动程序只能理解 Date 类型。

因此,假设在我的代码中使用新的 Java8 ZonedDateTime 来获取 UTC (ZonedDateTime.now(ZoneOffset.UTC)) ,那么如何将这个 ZonedDateTime 实例转换为“遗留”Date 类呢?

179490 次浏览

您可以使用构建在 Java8及更高版本中的 爪哇时间类来完成这项工作。

ZonedDateTime temporal = ...
long epochSecond = temporal.getLong(INSTANT_SECONDS);
int nanoOfSecond = temporal.get(NANO_OF_SECOND);
Date date = new Date(epochSecond * 1000 + nanoOfSecond / 1000000);

如果你只对现在感兴趣,那么简单地使用:

Date d = new Date();

可以将 ZonedDateTime 转换为即时,可以直接使用 Date。

Date.from(java.time.ZonedDateTime.now().toInstant());

博士

java.util.Date.from(  // Transfer the moment in UTC, truncating any microseconds or nanoseconds to milliseconds.
Instant.now() ;   // Capture current moment in UTC, with resolution as fine as nanoseconds.
)

虽然上面的代码没有任何意义。java.util.DateInstant都以 UTC 表示一个时刻,总是以 UTC 表示。上述守则的效力如下:

new java.util.Date()  // Capture current moment in UTC.

在这里使用 ZonedDateTime没有好处。如果您已经有一个 ZonedDateTime,通过提取一个 Instant来调整到 UTC。

java.util.Date.from(             // Truncates any micros/nanos.
myZonedDateTime.toInstant()  // Adjust to UTC. Same moment, same point on the timeline, different wall-clock time.
)

其他正确答案

Ssoltanid回答我正确地解决了您的具体问题,即如何将一个新学校的 java.time 对象(ZonedDateTime)转换为一个旧学校的 java.util.Date对象。从 ZonedDateTime 中提取 Instant并传递给 java.util.Date.from()

数据丢失

请注意,您将 数据丢失,因为 Instant新纪元开始跟踪 纳秒,而 java.util.Date从纪元开始跟踪 毫秒

diagram comparing resolutions of millisecond, microsecond, and nanosecond

你的问题和意见提出了其他问题。

保持服务器在协调世界时

您的服务器应该将其主机操作系统设置为 UTC,作为一般的最佳实践。JVM 将这个主机操作系统设置作为它的默认时区,在我所知道的 Java 实现中。

指定时区

但是您永远不应该依赖于 JVM 当前的默认时区。启动 JVM 时传递的标志可以设置另一个时区,而不是选择主机设置。更糟糕的是: 任何应用程序的任何线程中的任何代码在任何时候都可以调用 java.util.TimeZone::setDefault在运行时更改默认值!

卡珊德拉 Timestamp

任何 不错的数据库和驱动程序都应该自动处理将传递的日期时间调整为 UTC 以进行存储。我不使用卡桑德拉,但它似乎有一些基本的日期时间的支持。文档说它的 Timestamp类型是从同一个时代(以 UTC 表示的1970年的第一个时刻)开始的毫秒计数。

ISO 8601

此外,Cassandra 接受 ISO 8601标准格式的字符串输入。幸运的是,java.time 使用 ISO 8601格式作为解析/生成字符串的默认格式。Instant类的 toString实现会做得很好。

精度: 毫秒 VS 纳秒

但是首先我们需要将 ZonedDateTime 的纳秒精度降低到毫秒。一种方法是使用毫秒创建一个新的 Instant。幸运的是,java.time 有一些方便的方法可以在毫秒之间进行转换。

示例代码

下面是 Java8Update60中的一些示例代码。

ZonedDateTime zdt = ZonedDateTime.now( ZoneId.of( "America/Montreal" ) );
…
Instant instant = zdt.toInstant();
Instant instantTruncatedToMilliseconds = Instant.ofEpochMilli( instant.toEpochMilli() );
String fodderForCassandra = instantTruncatedToMilliseconds.toString();  // Example: 2015-08-18T06:36:40.321Z

或者根据这个 Cassandra Java 驱动程序文档,您可以传递一个 java.util.Date实例(不要与 java.sqlDate混淆)。所以你可以从上面的代码中的 instantTruncatedToMilliseconds得到一个 j.u. 日期。

java.util.Date dateForCassandra = java.util.Date.from( instantTruncatedToMilliseconds );

如果经常这样做,您可以创建一个俏皮话。

java.util.Date dateForCassandra = java.util.Date.from( zdt.toInstant() );

但是创建一个小的实用方法会更简单。

static public java.util.Date toJavaUtilDateFromZonedDateTime ( ZonedDateTime zdt ) {
Instant instant = zdt.toInstant();
// Data-loss, going from nanosecond resolution to milliseconds.
java.util.Date utilDate = java.util.Date.from( instant ) ;
return utilDate;
}

注意所有这些代码中与问题中的区别。“问题”的代码试图将 ZonedDateTime 实例的时区调整为 UTC。但这没有必要。概念上:

ZonedDateTime = Instant + ZoneId

我们只是提取 Instant 部分,它已经是 UTC (基本上是 UTC,阅读类文档以获得精确的细节)。


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


关于 爪哇时间

< em > java.time 框架内置于 Java8及更高版本中。这些类取代了令人讨厌的旧的 遗产日期时间类,如 java.util.DateCalendarSimpleDateFormat

现在在 维修模式中的 尤达时间项目建议迁移到 爪哇时间类。

要了解更多,请参阅 < em > Oracle 教程 。并搜索堆栈溢出许多例子和解释。规范是 JSR 310

您可以直接与数据库交换 爪哇时间对象。使用与 JDBC 4.2或更高版本兼容的 JDBC 驱动程序。不需要字符串,不需要 java.sql.*类。

从哪里获得 java.time 类?

Table of which java.time library to use with which version of Java or Android

< strong > Three Ten-Ultra 项目使用其他类扩展 java.time。这个项目是将来可能添加 java.time 的试验场。您可以在这里找到一些有用的类,比如 IntervalYearWeekYearQuarter更多

下面是一个将当前系统时间转换为 UTC 的示例。它涉及到将 ZonedDateTime 格式化为 String,然后使用 java.text DateFormat 将 String 对象解析为 date 对象。

    ZonedDateTime zdt = ZonedDateTime.now(ZoneOffset.UTC);
final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd HH:mm:ss");
final DateFormat FORMATTER_YYYYMMDD_HH_MM_SS = new SimpleDateFormat("yyyyMMdd HH:mm:ss");
String dateStr = zdt.format(DATETIME_FORMATTER);


Date utcDate = null;
try {
utcDate = FORMATTER_YYYYMMDD_HH_MM_SS.parse(dateStr);
}catch (ParseException ex){
ex.printStackTrace();
}

如果你使用的是用于 Android 的 310退后,而不能使用新的 Date.from(Instant instant)(它需要最低的 API 26) ,你可以使用:

ZonedDateTime zdt = ZonedDateTime.now();
Date date = new Date(zdt.toInstant().toEpochMilli());

或:

Date date = DateTimeUtils.toDate(zdt.toInstant());

请同时阅读 巴兹尔 · 布尔克的回答中的建议

我用这个。

public class TimeTools {


public static Date getTaipeiNowDate() {
Instant now = Instant.now();
ZoneId zoneId = ZoneId.of("Asia/Taipei");
ZonedDateTime dateAndTimeInTai = ZonedDateTime.ofInstant(now, zoneId);
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInTai.toString().substring(0, 19).replace("T", " "));
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
}

因为 Date.from (java.time. ZonedDateTime.ofInstant (now,zoneId) . toInstant ()) ; 这不是工作! ! ! 如果您在计算机中运行应用程序,这不是问题。但是如果您在 AWS、 Docker 或 GCP 的任何区域中运行,都会产生问题。因为计算机不是你在云上的时区。您应该在代码中设置正确的时区。例如,亚洲/台北。然后它将在 AWS 或 Docker 或 GCP 中更正。

public class App {
public static void main(String[] args) {
Instant now = Instant.now();
ZoneId zoneId = ZoneId.of("Australia/Sydney");
ZonedDateTime dateAndTimeInLA = ZonedDateTime.ofInstant(now, zoneId);
try {
Date ans = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateAndTimeInLA.toString().substring(0, 19).replace("T", " "));
System.out.println("ans="+ans);
} catch (ParseException e) {
}
Date wrongAns = Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant());
System.out.println("wrongAns="+wrongAns);
}
}

对于像 beeHuang 这样的 Docker 应用程序,您应该设置时区。

你也可以选择使用 和 ZoneSameLocal,例如:

2014-07-01 T00:00 + 02:00 [ GMT + 02:00]由

Date.from(zonedDateTime.withZoneSameLocal(ZoneId.systemDefault()).toInstant())

2014年7月1日星期二

Date.from(zonedDateTime.toInstant())

世界协调时2014年6月30日星期一22:00:00

接受的答案对我不起作用。返回的 Date 始终是本地 Date,而不是原始时区的 Date。我住在 UTC + 2。

//This did not work for me
Date.from(java.time.ZonedDateTime.now().toInstant());

我想出了两种从 ZonedDateTime 中获取正确 Date 的替代方法。

假设你有夏威夷的分区日期时间

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneId.of("US/Hawaii"); // UTC-10

或者按照原来的要求

Instant zulu = Instant.now(); // GMT, UTC+0
ZonedDateTime zdt = zulu.atZone(ZoneId.of("UTC"));

选择1

我们可以使用 java.sql.Timestamp。它很简单,但它可能也会削弱你的编程完整性

Date date1 = Timestamp.valueOf(zdt.toLocalDateTime());

选择2

我们创建的日期从米利斯(回答 给你较早)。注意,本地 ZoneOffset 是必须的。

ZoneOffset localOffset = ZoneOffset.systemDefault().getRules().getOffset(LocalDateTime.now());
long zonedMillis = 1000L * zdt.toLocalDateTime().toEpochSecond(localOffset) + zdt.toLocalDateTime().getNano() / 1000000L;
Date date2 = new Date(zonedMillis);