将LocalDate转换为java.util.Date,以及最简单的相反转换?

183

有没有一种简单的方法将 Java 8 中引入的 LocalDate 转换为 java.util.Date 对象?

我所说的“简单”是指比这更简单的方法:

Date date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());

这对我来说有些别扭。

由于我们只对日期部分感兴趣,而且两个对象中都没有时区信息,为什么要明确引入时区?应该隐式地采用午夜时间和系统默认时区进行转换。


4
如果您正在使用Java 8,则最好完全避免使用java.util.Date - Reimeus
4
由于我们仅对日期部分感兴趣,问题在于java.util.Date默认情况下不仅希望表示日期(年/月/日)的信息,还要包括一天中的时间信息,因此我们需要以某种方式提供此信息(这里使用 atStartOfDay 来帮助)。 - Pshemo
6
日期时间(Date)表示一个精确的瞬间,而本地日期时间(LocalDate)不是。它只是一组字段(set of fields)。因此,你需要指定一个时区将这个字段集转换成一个瞬间:例如,巴黎和纽约的2015年10月11日00:00并不代表同一个瞬间。 - JB Nizet
8
如果他使用的是旧的API,那么他可能别无选择,只能使用Date。例如,JPA 2.1的@Basic注解直接识别DateCalendar,但仅支持将Instant作为可序列化类型。我认为他在新代码中使用新的日期时间API,并使用Date进行互操作是正确的做法。 - scottb
5
@George:DateInstant 的通用合同都包括时间概念。因此,在 LocalDateDate 之间相互转换意味着您必然涉及时间概念,即使您不打算使用时间信息。如果没有知道 "某个本地时区" 是什么,就无法将 "2015年9月7日在某个本地时区" (LocalDate) 转换为 "自 1970 年 1 月 1 日 UTC 午夜以来的毫秒数" (Date)。 - scottb
显示剩余4条评论
7个回答

196

简短概述

有没有一种简单的方法将Java 8中引入的LocalDate转换为java.util.Date对象? '简单'的意思是比这个更简单。

没有。您已经正确地完成了操作,并且尽可能简洁。

java.util.Date.from(                     // Convert from modern java.time class to troublesome old legacy class.  DO NOT DO THIS unless you must, to inter operate with old code not yet updated for java.time.
    myLocalDate                          // `LocalDate` class represents a date-only, without time-of-day and without time zone nor offset-from-UTC. 
    .atStartOfDay(                       // Let java.time determine the first moment of the day on that date in that zone. Never assume the day starts at 00:00:00.
        ZoneId.of( "America/Montreal" )  // Specify time zone using proper name in `continent/region` format, never 3-4 letter pseudo-zones such as “PST”, “CST”, “IST”. 
    )                                    // Produce a `ZonedDateTime` object. 
    .toInstant()                         // Extract an `Instant` object, a moment always in UTC.
)

阅读以下问题,然后思考一下。它怎么可能更简单?如果你问我一个约会什么时候开始,我除了问你“在哪里?”之外还能如何回答呢?巴黎FR比蒙特利尔CA早一个新的一天开始,而加尔各答IN甚至比奥克兰NZ更早,这些都是不同的时刻。
因此,在将仅日期(LocalDate)转换为日期时间时,我们必须应用时区(ZoneId)以获取分区值(ZonedDateTime),然后进入UTC(Instant)以匹配java.util.Date的定义。
首先,尽可能避免使用旧的旧日期时间类,如java.util.Date。它们设计不良,令人困惑且麻烦。它们被java.time类所取代,实际上有很多原因。
但是如果必须使用,可以将其转换为/从java.time类型。查找添加到旧类中的新转换方法。

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

java.util.Datejava.time.LocalDate

请注意,java.util.Date 是一个误称,因为它代表了一个带有时间和日期的 UTC 时间。相比之下,LocalDate 类表示仅包含日期值而不包含时间和时区。

java.util.Date 转换到 java.time 意味着转换为等效的 java.time.Instant 类。Instant 类表示时间线上的瞬间,在 UTC 时区内,精度为 纳秒(小数部分最多九位数字)。

Instant instant = myUtilDate.toInstant();

LocalDate类表示仅包含日期而不包含时间和时区的值。

时区对于确定日期非常重要。对于任何给定的时刻,日期因时区而异。例如,在法国巴黎午夜过后几分钟是新的一天,而在加拿大魁北克省蒙特利尔仍然是“昨天”。

因此,我们需要将那个Instant转换为一个时区。我们使用ZoneId来获取一个ZonedDateTime

ZoneId z = ZoneId.of( "America/Montreal" );
ZonedDateTime zdt = instant.atZone( z );

从那里开始,要求仅日期,即LocalDate

LocalDate ld = zdt.toLocalDate();

java.time.LocalDatejava.util.Date

如果要从java.time.LocalDate转换为java.util.Date,意味着我们从仅包含日期的对象转换为日期和时间的对象。因此,我们必须指定一天中的某个时间。您可能想选择一天中的第一时刻作为转换后的时间。但是,请不要假设这个时间就是00:00:00。由于夏令时等原因,第一时刻可能是其他时间,例如01:00:00。让java.time通过调用atStartOfDay方法来决定该值。

ZonedDateTime zdt = myLocalDate.atStartOfDay( z );

现在提取一个Instant
Instant instant = zdt.toInstant();

通过调用 from(Instant)将那个Instant转换为java.util.Date

java.util.Date d = java.util.Date.from( instant );

更多信息


关于 java.time

java.time 框架是内置于 Java 8 及更高版本中的。这些类替代了老旧且麻烦的 传统 日期时间类,如 java.util.DateCalendarSimpleDateFormat

要了解更多,请查看Oracle教程。并在Stack Overflow上搜索许多示例和解释。规范是JSR 310

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

您可以直接使用与您的数据库兼容的JDBC 4.2或更高版本的JDBC驱动程序,无需使用字符串或java.sql.*类。Hibernate 5和JPA 2.2支持java.time对象。

如何获取java.time类?

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

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


4
我喜欢这个答案,因为它包含了“太长,未读”和“简短扼要”两部分,谢谢。 - conny

145

免责声明:虽然下面的答案可行,但不建议在生产代码中使用。在这种情况下,应该遵循Basil的答案。

实际上是有方法的。在java.sql.Date对象中有一个静态方法valueOf,正好可以做到这一点。因此我们有:

java.util.Date date = java.sql.Date.valueOf(localDate);

就是这样。没有明确设置时区,因为本地时区被隐式地采用。

来自文档:

提供的 LocalDate 被解释为本地时区中的本地日期。

java.sql.Datejava.util.Date 的子类,因此结果也是一个 java.util.Date

对于反向操作,java.sql.Date 类中有一个 toLocalDate 方法。所以我们有:

LocalDate ld = new java.sql.Date(date.getTime()).toLocalDate();


4
原始问题日期也在系统时区中。在我看来,使用java.sql.Date表示实际日期并不是那么牵强。 - eckes
17
这个答案不能被推荐,因为它缺乏对不同时区的支持并混淆了两种不同的类型,其中继承关系仅表示基于实现的依赖关系。类型java.sql.Date只应在JDBC上下文中使用。最佳答案请查看@Basil Bourque的回答。 - Meno Hochschild
5
正如Hochschild所说,类文档告诉我们忽略java.util.Date和java.sql.Date之间的继承子类关系,告诉我们不要像此答案那样做。此外,valueOf方法并没有避开时区问题,它只是在将时间设置为午夜时,悄悄地应用JVM当前默认时区来隐藏这个问题。因此,“没有时区,没有复杂性”的说法是不正确的:确实涉及到了时区,也确实有复杂性,你只是选择忽略它们。Java 8引入了java.time类库,它们是有原因存在的,建议使用它们。 - Basil Bourque
82
作为 java.time.* 库的作者,我对有40多个人认为这篇文章是正确答案感到遗憾。正确答案在这里和下面的Basil的回答中。使用 java.sql.Date 是一个可怕的hack,正如其他评论所述,这种方法隐藏了时区转换,这是一个不好的事情。多年来已经知道 java.sql.Date 不应该扩展 java.util.Date。最后,在 Java 9 中使用模块时,java.sql 类将成为单独的依赖项,因此使用这种 hack 将产生后果。 - JodaStephen
@JodaStephen 我认为问题在于旧的JDBC API,它们返回java.sql.Dates,这些日期可以轻松地被不知情的中间类向下转换为java.util.Date,然后当您调用java.util.Date::toInstant()时,您会从java.sql.Date得到一个UnsupportedOperationException :( - Adam
5
这只是一个勉强能用的方法,请忽略这个回答。相反,可以查看 @BasilBourque 的答案。链接在这里:https://dev59.com/31wY5IYBdhLWcg3wD0LG#40143687 - Prakash K

29

日期 -> 本地日期:

LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

将LocalDate转换为Date:

Date date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());

3
如果你的 java.util.Date 实际上是一个 java.sql.Date,那么这将会给你一个 UnsupportedOperationException 异常。 - Adam

5

将LocalDateTime转换为java.util.Date

    LocalDateTime localDateTime = LocalDateTime.now();

    ZonedDateTime zonedDateTime = localDateTime.atZone(ZoneOffset.systemDefault());

    Instant instant = zonedDateTime.toInstant();

    Date date = Date.from(instant);

System.out.println("Result Date is : "+date);

4

将日期转换为本地日期

Date date = new Date();
LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();

LocalDate转换为Date

LocalDate localDate = LocalDate.now();
Date date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());

-2
我用以下解决方案解决了这个问题。
  import org.joda.time.LocalDate;
  Date myDate = new Date();
  LocalDate localDate = LocalDate.fromDateFields(myDate);
  System.out.println("My date using Date" Nov 18 11:23:33 BRST 2016);
  System.out.println("My date using joda.time LocalTime" 2016-11-18);

在这种情况下,localDate 以 "yyyy-MM-dd" 的格式打印您的日期。

1
请引用该方法的文档。我在java.time.LocalDate类的文档中没有看到该方法。 - Basil Bourque
我的错。我的错。我忘记复制变量了。 - estevamdf
2
你可以编辑你的答案来纠正错别字,并澄清你使用的方法的来源。这个方法似乎来自Joda-Time,但是这个问题是关于java.time.LocalDate的。Joda-Time项目现在处于维护模式,团队建议迁移到java.time。 - Basil Bourque
感谢 @BasilBourque 的观察。在这种情况下,我使用了包 org.joda.time 的类 LocalDate。修正回答我用以下解决方案解决了这个问题:`import org.joda.time.LocalDate Date myDate = new Date (); LocalDate localDate = LocalDate.fromDateFields (myDate);System.out.println("My date using Date" Nov 18 11:23:33 BRST 2016); System.out.println("My date using joda.time LocalTime" 2016-11-18); ` - estevamdf
1
Joda-Time 项目现已进入维护模式。该团队建议迁移到 Java 8 及以后版本内置的 java.time 类。对于 Java 6 和 7,请使用 ThreeTen-Backport 项目。 - Basil Bourque
显示剩余2条评论

-5

您可以将java.util.Date对象转换为String对象,这将按照yyyy-mm-dd格式化日期。

LocalDate具有一个parse方法,它将其转换为LocalDate对象。字符串必须表示有效日期,并使用DateTimeFormatter.ISO_LOCAL_DATE进行解析。

从Date到LocalDate

LocalDate.parse(Date.toString())

2
你显然没有测试你的代码建议,否则你会看到一个异常,因为java.time.LocalDate期望一个ISO格式的字符串,而不是java.util.Date的标准输出。虽然应该被点踩,但今天我心情很好;-) 这里最好的答案是由Basil Bourque给出的。 - Meno Hochschild

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接