繁体   English   中英

如何将 ZonedDateTime 转换为日期?

[英]How to convert ZonedDateTime to Date?

我正在尝试在我的数据库中设置一个与服务器无关的日期时间,我相信这样做的最佳做法是设置一个 UTC 日期时间。 我的数据库服务器是 Cassandra,Java 的数据库驱动程序只能识别日期类型。

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

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

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

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

虽然上面的代码没有意义。 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跟踪自纪元以来的毫秒

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

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

保持服务器的UTC

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

指定时区

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

Cassandra Timestamp类型

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

ISO 8601

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

精度:毫秒级与Nanosecord

但首先我们需要将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驱动程序文档 ,您可以传递一个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 = Instant + ZoneId

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


关于java.time

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

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

要了解更多信息,请参阅Oracle教程 并搜索Stack Overflow以获取许多示例和解释。 规范是JSR 310

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

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展了java.time。 该项目是未来可能添加到java.time的试验场。 您可以在这里找到一些有用的类,比如IntervalYearWeekYearQuarter ,和更多

以下是将当前系统时间转换为UTC的示例。 它涉及将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();
    }

如果您使用Android的ThreeTen backport并且无法使用较新的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的回答中的建议

接受的答案对我不起作用。 返回的日期始终是本地日期,而不是原始时区的日期。 我住在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创建日期(之前 在这里回答)。 请注意,本地 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);

您可以使用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();

我用这个。

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的任何区域中运行,都会产生问题。 因为计算机不是云上的时区。 您应该在Code中设置正确的时区。 例如,亚洲/台北。 然后它将在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这样的码头工具应用程序,你应该设置你的时区。

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

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

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

星期二01月01 00:00:00 CEST 2014

Date.from(zonedDateTime.toInstant())

2014年1月30日22:00:00 UTC

暂无
暂无

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

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