简体   繁体   English

如何将 ZonedDateTime 转换为日期?

[英]How to convert ZonedDateTime to Date?

I am trying to set a server agnostic date time in my database and I believe the best practice to do so is to set a UTC DateTime.我正在尝试在我的数据库中设置一个与服务器无关的日期时间,我相信这样做的最佳做法是设置一个 UTC 日期时间。 My db server is Cassandra and the db driver for Java understands only the Date type.我的数据库服务器是 Cassandra,Java 的数据库驱动程序只能识别日期类型。

So assuming that in my code I am using the new Java 8 ZonedDateTime to get the UTC now ( ZonedDateTime.now(ZoneOffset.UTC) ), how can I convert this ZonedDateTime instance to the "legacy" Date class?因此,假设在我的代码中,我现在使用新的 Java 8 ZonedDateTime 来获取 UTC( ZonedDateTime.now(ZoneOffset.UTC) ),我如何将此 ZonedDateTime 实例转换为“传统”Date 类?

您可以将ZonedDateTime转换为瞬间,您可以直接使用Date。

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

tl;dr TL;博士

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.
)

Though there was no point in that code above. 虽然上面的代码没有意义。 Both java.util.Date and Instant represent a moment in UTC, always in UTC. java.util.DateInstant表示以UTC为单位的时刻,始终为UTC。 Code above has same effect as: 上面的代码具有相同的效果:

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

No benefit here to using ZonedDateTime . 使用ZonedDateTime没有任何好处。 If you already have a ZonedDateTime , adjust to UTC by extracting a Instant . 如果您已有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.
)

Other Answer Correct 其他答案正确

The Answer by ssoltanid correctly addresses your specific question, how to convert a new-school java.time object ( ZonedDateTime ) to an old-school java.util.Date object. ssoltanid答案正确地解决了您的具体问题,如何将新学校的java.time对象( ZonedDateTime )转换为老式的java.util.Date对象。 Extract the Instant from the ZonedDateTime and pass to java.util.Date.from() . 从ZonedDateTime中提取Instant并传递给java.util.Date.from()

Data Loss 数据丢失

Note that you will suffer data loss , as Instant tracks nanoseconds since epoch while java.util.Date tracks milliseconds since epoch. 请注意,您将遭受数据丢失 ,因为Instant跟踪自纪元以来的纳秒 ,而java.util.Date跟踪自纪元以来的毫秒

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

Your Question and comments raise other issues. 您的问题和意见提出了其他问题。

Keep Servers In UTC 保持服务器的UTC

Your servers should have their host OS set to UTC as a best practice generally. 您的服务器通常应将其主机操作系统设置为UTC作为最佳做法。 The JVM picks up on this host OS setting as its default time zone, in the Java implementations that I'm aware of. 在我所知道的Java实现中,JVM将此主机操作系统设置作为其默认时区。

Specify Time Zone 指定时区

But you should never rely on the JVM's current default time zone. 但是你永远不应该依赖JVM的当前默认时区。 Rather than pick up the host setting, a flag passed when launching a JVM can set another time zone. 启动JVM时传递的标志可以设置另一个时区,而不是选择主机设置。 Even worse: Any code in any thread of any app at any moment can make a call to java.util.TimeZone::setDefault to change that default at runtime! 更糟糕的是:任何时候任何应用程序的任何线程中的任何代码都可以调用java.util.TimeZone::setDefault来在运行时更改该默认值!

Cassandra Timestamp Type Cassandra Timestamp类型

Any decent database and driver should automatically handle adjusting a passed date-time to UTC for storage. 任何体面的数据库和驱动程序都应自动处理调整传递的UTC日期时间以进行存储。 I do not use Cassandra, but it does seem to have some rudimentary support for date-time. 我不使用Cassandra,但似乎对日期时间有一些基本的支持。 The documentation says its Timestamp type is a count of milliseconds from the same epoch (first moment of 1970 in UTC). 文档说它的Timestamp类型是相同纪元(1970年第一个UTC时刻)的毫秒数。

ISO 8601 ISO 8601

Furthermore, Cassandra accepts string inputs in the ISO 8601 standard formats. 此外,Cassandra接受ISO 8601标准格式的字符串输入。 Fortunately, java.time uses ISO 8601 formats as its defaults for parsing/generating strings. 幸运的是,java.time使用ISO 8601格式作为解析/生成字符串的默认值。 The Instant class' toString implementation will do nicely. Instant类的toString实现可以很好地完成。

Precision: Millisecond vs Nanosecord 精度:毫秒级与Nanosecord

But first we need to reduce the nanosecond precision of ZonedDateTime to milliseconds. 但首先我们需要将ZonedDateTime的纳秒精度降低到毫秒。 One way is to create a fresh Instant using milliseconds. 一种方法是使用毫秒创建一个新的Instant。 Fortunately, java.time has some handy methods for converting to and from milliseconds. 幸运的是,java.time有一些方便的转换为毫秒和从毫秒转换的方法。

Example Code 示例代码

Here is some example code in Java 8 Update 60. 以下是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

Or according to this Cassandra Java driver doc , you can pass a java.util.Date instance (not to be confused with java.sqlDate ). 或者根据这个Cassandra Java驱动程序文档 ,您可以传递一个java.util.Date实例(不要与java.sqlDate混淆)。 So you could make a juDate from that instantTruncatedToMilliseconds in the code above. 所以你可以从上面代码中的instantTruncatedToMilliseconds创建一个juDate。

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

If doing this often, you could make a one-liner. 如果经常这样做,你可以做一个单行。

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

But it would be neater to create a little utility method. 但是创建一个小实用方法会更简洁。

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

Notice the difference in all this code than in the Question. 请注意所有这些代码与问题中的区别。 The Question's code was trying to adjust the time zone of the ZonedDateTime instance to UTC. 问题的代码试图将ZonedDateTime实例的时区调整为UTC。 But that is not necessary. 但这不是必要的。 Conceptually: 概念:

ZonedDateTime = Instant + ZoneId ZonedDateTime = Instant + ZoneId

We just extract the Instant part, which is already in UTC (basically in UTC, read the class doc for precise details). 我们只提取已经是UTC的Instant部分(基本上是UTC,阅读类doc以获得精确的细节)。


About java.time 关于java.time

The java.time framework is built into Java 8 and later. java.time框架内置于Java 8及更高版本中。 These classes supplant the troublesome old legacy date-time classes such as java.util.Date , Calendar , & SimpleDateFormat . 这些类取代了麻烦的旧遗留日期时间类,如java.util.DateCalendarSimpleDateFormat

The Joda-Time project, now in maintenance mode , advises migration to the java.time classes. 现在处于维护模式Joda-Time项目建议迁移到java.time类。

To learn more, see the Oracle Tutorial . 要了解更多信息,请参阅Oracle教程 And search Stack Overflow for many examples and explanations. 并搜索Stack Overflow以获取许多示例和解释。 Specification is JSR 310 . 规范是JSR 310

You may exchange java.time objects directly with your database. 您可以直接与数据库交换java.time对象。 Use a JDBC driver compliant with JDBC 4.2 or later. 使用符合JDBC 4.2或更高版本的JDBC驱动程序 No need for strings, no need for java.sql.* classes. 不需要字符串,不需要java.sql.*类。

Where to obtain the java.time classes? 从哪里获取java.time类?

The ThreeTen-Extra project extends java.time with additional classes. ThreeTen-Extra项目使用其他类扩展了java.time。 This project is a proving ground for possible future additions to java.time. 该项目是未来可能添加到java.time的试验场。 You may find some useful classes here such as Interval , YearWeek , YearQuarter , and more . 您可以在这里找到一些有用的类,比如IntervalYearWeekYearQuarter ,和更多

Here is an example converting current system time to UTC. 以下是将当前系统时间转换为UTC的示例。 It involves formatting ZonedDateTime as a String and then the String object will be parsed into a date object using java.text DateFormat. 它涉及将ZonedDateTime格式化为String,然后使用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();
    }

If you are using the ThreeTen backport for Android and can't use the newer Date.from(Instant instant) (which requires minimum of API 26) you can use: 如果您使用Android的ThreeTen backport并且无法使用较新的Date.from(Instant instant) (需要最少的API 26),您可以使用:

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

or: 要么:

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

Please also read the advice in Basil Bourque's answer 另请阅读Basil Bourque的回答中的建议

The accepted answer did not work for me.接受的答案对我不起作用。 The Date returned is always the local Date and not the Date for the original Time Zone.返回的日期始终是本地日期,而不是原始时区的日期。 I live in UTC+2.我住在UTC+2。

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

I have come up with two alternative ways to get the correct Date from a ZonedDateTime.我想出了两种从 ZonedDateTime 获取正确日期的替代方法。

Say you have this ZonedDateTime for Hawaii假设您有夏威夷的 ZonedDateTime

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

or for UTC as asked originally或最初询问的UTC

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

Alternative 1备选方案 1

We can use java.sql.Timestamp.我们可以使用 java.sql.Timestamp。 It is simple but it will probably also make a dent in your programming integrity这很简单,但它也可能会削弱您的编程完整性

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

Alternative 2备选方案 2

We create the Date from millis (answered here earlier).我们从millis创建日期(之前 在这里回答)。 Note that local ZoneOffset is a must.请注意,本地 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);

You can do this using the java.time classes built into Java 8 and later. 您可以使用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);

如果您现在只对此感兴趣,那么只需使用:

Date d = new Date();

I use this. 我用这个。

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

Because Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); 因为Date.from(java.time.ZonedDateTime.ofInstant(now, zoneId).toInstant()); It's not work!!! 这不行! If u run your application in your computer, it's not problem. 如果你在计算机上运行你的应用程序,那不是问题。 But if you run in any region of AWS or Docker or GCP, it will generate problem. 但是如果您在AWS或Docker或GCP的任何区域中运行,都会产生问题。 Because computer is not your timezone on Cloud. 因为计算机不是云上的时区。 You should set your correctly timezone in Code. 您应该在Code中设置正确的时区。 For example, Asia/Taipei. 例如,亚洲/台北。 Then it will correct in AWS or Docker or 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);
    }
}

For a docker application like beehuang commented you should set your timezone. 对于像beehuang这样的码头工具应用程序,你应该设置你的时区。

Alternatively you can use withZoneSameLocal . 或者,您可以使用withZoneSameLocal For example: 例如:

2014-07-01T00:00+02:00 [GMT+02:00] is converted by 2014-07-01T00:00 + 02:00 [GMT + 02:00]由...转换

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

to Tue Jul 01 00:00:00 CEST 2014 and by 星期二01月01 00:00:00 CEST 2014

Date.from(zonedDateTime.toInstant())

to Mon Jun 30 22:00:00 UTC 2014 2014年1月30日22:00:00 UTC

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM