怎么把ZonedDateTime转换成日期?


101

我正在尝试在数据库中设置服务器不可知日期时间,并且我相信这样做的最佳做法是设置UTC DateTime。我的数据库服务器是Cassandra,Java的数据库驱动程序仅理解Date类型。

因此,假设在我的代码中我正在使用新的Java 8 ZonedDateTime立即获取UTC(ZonedDateTime.now(ZoneOffset.UTC)),如何将ZonedDateTime实例转换为“旧版” Date类?


有内置到Java两个“日期”类,java.util.Datejava.sql.Date
罗勒·布尔克

Answers:


161

您可以将ZonedDateTime转换为瞬时值,可以将其直接与Date一起使用。

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

27
不,这将是您区域系统默认的当前日期。
Slim Soltani Dridi

6
@MilenKovachev您的问题没有任何意义-日期没有时区-它仅表示时间的瞬间。
assylias 2015年

1
@assylias实际上,您的声明没有任何意义。时间的存储格式表示时区。Date是基于UTC的,不幸的是,Java做了一些愚蠢的事情,并没有将其视作此类事情,并且最重要的是,它认为时间是本地TZ而不是UTC。Data,LocalDateTime,ZonedDateTime存储数据的方式暗含时区。它们的使用方式就像不存在TZ,这完全是错误的。java.util.Date隐式具有JVM默认值的TZ。人们将它视为任何不同的事实(包括用于它的Java文档!)只是坏人。
大卫

5
@David uh no-a Date是自纪元以来的毫秒数-因此它与UTC有关。如果打印它,将使用默认时区,但是Date类不了解用户的时区...例如,请参阅docs.oracle.com/javase/tutorial/datetime/iso/legacy中的“映射”部分.html和LocalDateTime明确地未提及时区-这可能会引起混淆...
assylias 2015年

1
您的答案不必要地涉及ZonedDateTime。该java.time.Instant班是直接更换java.util.Date,均代表在UTC片刻尽管Instant利用纳秒而不是毫秒更精细的分辨率。Date.from( Instant.now() )应该是您的解决方案。或就此而言,new Date()具有相同效果的就是捕获UTC中的当前时刻。
罗勒·布尔克

64

tl; dr

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中的时刻。上面的代码与以下内容具有相同的作用:

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

使用这里没有好处ZonedDateTime。如果您已有一个ZonedDateTime,请通过提取一个来适应UTC Instant

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对象。Instant从ZonedDateTime中提取并传递给java.util.Date.from()

资料遗失

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

该图比较了毫秒,微秒和纳秒的分辨率

您的问题和评论提出了其他问题。

使服务器保持UTC状态

通常,最好将服务器的主机操作系统设置为UTC。在我知道的Java实现中,JVM将此主机OS设置作为其默认时区。

指定时区

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

卡桑德拉Timestamp

任何体面的数据库和驱动程序都应自动处理调整传递给UTC的日期时间以进行存储。我不使用Cassandra,但是它似乎对日期时间有一些基本的支持。文档说它的Timestamp类型是同一时期(UT​​C 1970年的第一时刻)以来的毫秒数。

ISO 8601

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

精度:毫秒vs纳秒级

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

范例程式码

这是Java 8 Update 60中的一些示例代码。

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驱动程序doc,您可以传递一个java.util.Date实例(不要与混淆java.sqlDate)。因此,您可以根据instantTruncatedToMilliseconds上面的代码创建一个juDate 。

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 =即时+ ZoneId

我们只提取已在UTC中使用的Instant部分(基本上在UTC中,请阅读类文档以获取详细信息)。


Java中的日期时间类型表(现代和旧式)


关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar,和SimpleDateFormat

现在处于维护模式Joda-Time项目建议迁移到java.time类。

要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

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

在哪里获取java.time类?

与哪个版本的Java或Android一起使用的哪个java.time库的表

ThreeTen-额外项目与其他类扩展java.time。该项目是将来可能向java.time添加内容的试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多


精彩的解释,非常详尽。非常感谢!
Gianmarco F.

4

这是将当前系统时间转换为UTC的示例。它涉及将ZonedDateTime格式化为字符串,然后使用java.text DateFormat将String对象解析为日期对象。

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

4

如果您正在使用Android 的ThreeTen反向端口,并且不能使用较新的版本Date.from(Instant instant)(需要最少的API 26),则可以使用:

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

要么:

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

请同时阅读Basil Bourque答案中的建议


1
ThreeTen Backport(和ThreeTenABP)包括DateTimeUtils带有转换方法的类,因此我将使用Date date = DateTimeUtils.toDate(zdt.toInstant());`。它不是那么低级。
Ole VV

1

您可以使用Java 8及更高版本中内置的java.time类来完成此操作。

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

抱歉,时间常数和所有这些常量来自哪里?
Milen Kovachev,2015年

@MilenKovachev ZonedDateTime是Temporal的一个实例
Peter Lawrey

谢谢,但您应该提到您已编辑了答案,以免我的评论显得愚蠢。
Milen Kovachev 2015年

2
@MilenKovachev,您可以删除评论,但这不是一个愚蠢的问题。
彼得·劳瑞

1
我不明白为什么这个答案被否决了。 Instants跟踪秒和纳秒。 Dates的追踪毫秒数。从一个到另一个的这种转换是正确的。
斯科特,2015年

1

我用这个

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

1
这似乎是7个答案中最复杂的方法之一。有什么好处吗?我不会。确实没有必要进行格式化和解析。
Ole VV

谢谢你问。因为Date错误Ans = Date.from(java.time.ZonedDateTime.ofInstant(now,zoneId).toInstant());; 这是行不通的!
beehuang

我在时区17:08之前向您运行了几秒钟的片段,并得到ans=Mon Jun 18 01:07:56 CEST 2018,这是不正确的,然后是wrongAns=Sun Jun 17 17:07:56 CEST 2018,这是正确的。
Ole VV

在计算机上运行应用程序时,我没有任何问题。但是使用docker时出现问题。因为docker中的时区不是您计算机中的时区。您应该在代码中设置正确的时区。例如,亚洲/台北。然后它将在AWS,Docker,GCP或任何计算机中更正。
beehuang

@ OleV.V。你能听懂吗?或有什么问题吗?
beehuang

1

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

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

我想出了两种方法来从ZonedDateTime获取正确的日期。

假设您有这个ZonedDateTime用于夏威夷

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

或原先要求的UTC

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

我们从millis创建Date(前面已在此处回答)。请注意,本地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);

0

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

或者,您可以使用withZoneSameLocal。例如:

2014-07-01T00:00 + 02:00 [GMT + 02:00]转换为

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

CEST 2014年7月1日星期二00:00:00

Date.from(zonedDateTime.toInstant())

2014年UTC星期一6月30日22:00:00


-1

如果您仅对现在感兴趣,则只需使用:

Date d = new Date();

我现在对UTC感兴趣,但是现在。
Milen Kovachev,2015年

2
是的,现在就是UTC了,日期再好不过了。
Jacob Eckel

3
不,不会。在本地系统时区将是Now。日期不存储任何时区信息,但它使用当前系统时间,并忽略当前系统时区,因此永远不会将其转换回UTC
David

2
@David Date相对于UTC时代保持时间,因此,如果您从美国的系统中获取一个新的Date()并从日本的计算机中获取另一个对象,则它们将是相同的(请注意它们在内部保留的时间长)。
Jacob Eckel

1
@JacobEckel-是的,但是如果您在tz是美国tz的日期中将上午9点放入日期,然后更改为JP tz并在上午9点创建新日期,则Date中的内部值将有所不同。一旦将DST投入使用,就无法可靠地使用Date,除非您的应用程序始终以UTC专门运行,这可能会由于多种原因而发生更改,其中大多数与错误代码有关。
戴维(David)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.