在java.time.LocalDateTime和java.util.Date之间进行转换


484

Java 8具有用于日期和时间的全新API。此API中最有用的类之一是LocalDateTime,用于保存与时区无关的date-with-time值。

java.util.Date为此,可能有数百万行的代码使用遗留类。因此,当连接新旧代码时,将需要在两者之间进行转换。由于似乎没有直接的方法可以完成此操作,因此该怎么办呢?




Answers:


706

简短答案:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());

说明:(基于这个问题有关LocalDate

尽管有名称,它java.util.Date代表时间轴上的一个瞬间,而不是“日期”。存储在对象中的实际数据是long自1970-01-01T00:00Z(1970 GMT / UTC开始的午夜)以来的毫秒数。

java.util.DateJSR-310中的等效类是Instant,因此有方便的方法来回转换:

Date input = new Date();
Instant instant = input.toInstant();
Date output = Date.from(instant);

一个java.util.Date实例没有时区的概念。如果调用toString(),这可能看起来很奇怪java.util.Date,因为toString相对于时区。但是,该方法实际上在运行时使用了Java的默认时区来提供字符串。时区不是的实际状态的一部分java.util.Date

一个Instant还没有关于时区的任何信息。因此,要从转换Instant为本地日期时间,必须指定一个时区。这可能是默认区域- ZoneId.systemDefault()或可能是您的应用程序控制的时区,例如用户首选项中的时区。LocalDateTime有一个方便的工厂方法,它同时包含即时和时区:

Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());

相反,LocalDateTime时区是通过调用atZone(ZoneId)方法指定的。所述ZonedDateTime然后可以直接转化为Instant

LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());

请注意,从转换LocalDateTimeZonedDateTime具有引进意外行为的可能性。这是因为由于夏时制,并非每个本地日期时间都存在。在秋季/秋季,本地时间线重叠,同一本地日期时间出现两次。在春天,有一个间隙,一个小时消失了。有关atZone(ZoneId)转换功能的更多定义,请参见的Javadoc 。

总结,如果您java.util.Date将a 往返于a LocalDateTime并返回到a java.util.Date,则由于夏时制,您可能会得到不同的时刻。

附加信息:还有另一个差异会影响非常旧的日期。java.util.Date使用的日历是1582年10月15日更改的,其日期使用儒略历而不是公历。相比之下,java.time.*始终使用ISO日历系统(等同于公历)。在大多数使用情况下,ISO日历系统是您想要的,但是比较1582年之前的日期可能会看到奇怪的效果。


5
非常感谢您的明确解释。特别是因为为什么java.util.Date不包含时区,而是在期间打印toString()。活动官方文档在您发布时并未明确说明这一点。
樱桃

警告: LocalDateTime.ofInstant(date.toInstant()...不会像天真的预期那样表现。例如new Date(1111-1900,11-1,11,0,0,0);将成为1111-11-17 23:53:28使用这种方法。查看java.sql.Timestamp#toLocalDateTime()是否需要将结果显示1111-11-11 00:00:00在上一个示例中的实现。

2
我添加了有关非常古老的日期(1582年前)的部分。FWIW,您建议的修复方法很可能是错误的,因为java.util.Date中的1111-11-11与Java.time中的1111-11-18在历史上是同一天,因为日历系统不同(相差6.5分钟发生在1900年之前的许多时区)
JodaStephen

2
还值得一提的java.sql.Date#toInstantUnsupportedOperationException。因此,请勿toInstant在上使用RowMapper java.sql.ResultSet#getDate
LazerBass

132

这是我想出的(和所有日期时间难题一样,它可能会根据一些奇怪的时区-day年-日光调整:D来反证)

往返: Date<<->>LocalDateTime

鉴于: Date date = [some date]

(1)LocalDateTime<< Instant<<Date

    Instant instant = Instant.ofEpochMilli(date.getTime());
    LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

(2) Date<< Instant<<LocalDateTime

    Instant instant = ldt.toInstant(ZoneOffset.UTC);
    Date date = Date.from(instant);

例:

鉴于:

Date date = new Date();
System.out.println(date + " long: " + date.getTime());

(1)LocalDateTime<< Instant<< Date

Instant从创建Date

Instant instant = Instant.ofEpochMilli(date.getTime());
System.out.println("Instant from Date:\n" + instant);

创建DateInstant(不是必需的,但仅用于说明):

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

LocalDateTime从创建Instant

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
System.out.println("LocalDateTime from Instant:\n" + ldt);

(2)Date<< Instant<<LocalDateTime

Instant从创建LocalDateTime

instant = ldt.toInstant(ZoneOffset.UTC);
System.out.println("Instant from LocalDateTime:\n" + instant);

Date从创建Instant

date = Date.from(instant);
System.out.println("Date from Instant:\n" + date + " long: " + date.getTime());

输出为:

Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

Instant from Date:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

LocalDateTime from Instant:
2013-11-01T14:13:04.574

Instant from LocalDateTime:
2013-11-01T14:13:04.574Z

Date from Instant:
Fri Nov 01 07:13:04 PDT 2013 long: 1383315184574

2
@scottb是向我或提出问题的人讲话吗?至于我的想法,那么,这将是不错已经转换了JDK 8 API中明确表示-他们可能至少有这样做的。无论如何,有很多库将被重构以包括新的Java-8功能,并且知道如何做到这一点很重要。
协调员

4
@scottb为什么第三方案件不常见?它的该死的共同点。一个示例:JDBC 4及以下(希望不是5)。
拉曼2014年

5
作为一般的JSR-310规则,不需要使用纪元毫秒在类型之间进行转换。使用对象存在更好的选择,请参阅下面的完整答案。上面的答案也仅在使用像UTC这样的时区偏移时才完全有效-答案的某些部分在整个时区(例如America / New_York)中将不起作用。
JodaStephen 2014年

2
代替Instant.ofEpochMilli(date.getTime())date.toInstant()
山羊

1
@goat toInstant()看起来不错,但是它失败了java.sql.Date,arggggh!因此,最终更易于使用Instant.ofEpochMilli(date.getTime())
vadipp '19

22

如果您确定需要默认时区,则可以使用更方便的方法:

Date d = java.sql.Timestamp.valueOf( myLocalDateTime );

25
当然,这更容易,但是我不喜欢将与jdbc相关的东西与简单的Date处理(至少恕我直言)混合使用。
Enrico Giurin

9
抱歉,这是一个简单问题的糟糕解决方案。就像将5定义为等于奥运徽标中的环数一样。
Madbreaks '18

7

从新的API LocalDateTime转换为java.util.date时,以下内容似乎起作用:

Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());

反向转换可以(希望)以类似的方式实现...

希望能帮助到你...


5

一切都在这里:http : //blog.progs.be/542/date-to-java-time

“往返”的答案并不确切:当您这样做时

LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

如果您的系统时区不是UTC / GMT,请更改时间!


仅当您在每年的1个小时(秋天)与LocalDateTime的两次重叠时才这样做。弹簧向前移动不会造成问题。大多数时候,两个方向都可以正确转换。
大卫


3

我不确定这是最简单还是最好的方法,或者是否有任何陷阱,但是它可以工作:

static public LocalDateTime toLdt(Date date) {
    GregorianCalendar cal = new GregorianCalendar();
    cal.setTime(date);
    ZonedDateTime zdt = cal.toZonedDateTime();
    return zdt.toLocalDateTime();
}

static public Date fromLdt(LocalDateTime ldt) {
    ZonedDateTime zdt = ZonedDateTime.of(ldt, ZoneId.systemDefault());
    GregorianCalendar cal = GregorianCalendar.from(zdt);
    return cal.getTime();
}

3
LocalDateTime到绝对有一个陷阱Date。在夏时制转换时,a LocalDateTime可能不存在或出现两次。您需要确定每种情况下要发生的情况。
乔恩·斯基特

1
BTW,GregorianCalendar属于旧的笨拙的API,新的java.timeAPI旨在替代它
Vadzim '17

3

如果您在android上并使用threetenbp,则可以DateTimeUtils改用。

例如:

Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());

您无法使用,Date.from因为它仅在api 26+上受支持

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.