转换Java.time.LocalDateTime和java.util.Date之间的差异

614

Java 8有一个全新的日期和时间API。其中最有用的类之一是LocalDateTime,用于保存独立于时区的日期和时间。

使用旧的类java.util.Date来处理日期和时间的代码行数可能有数百万行。因此,在旧代码和新代码之间进行接口交互时,需要进行两者之间的转换。由于似乎没有直接的方法可以完成这个任务,那么该如何实现呢?


3
请点击此链接查看如何将Java8中的LocalDate和java.util.Date进行最简单的相互转换。 - George
3
这是Java官方文档中介绍使用ISO日期时间格式的遗留代码(Legacy Code)的部分。ISO日期时间格式是一种通用的日期和时间表示方式,并且在Java 8中已经得到了广泛支持。该文档提供了如何在旧版本的Java中使用这种格式的示例代码,同时还介绍了如何将其与Joda-Time库一起使用。 - Bax
可能是将java.util.Date转换为java.time.LocalDate的重复问题。 - Peter Perháč
9个回答

897

简短回答:

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虽然名字中带有“日期”(date),但实际上它表示的是时间轴上的一个瞬间。对象内部实际存储的数据是一个以自1970年01月01日00:00Z(1970年1月1日格林威治标准时间午夜)起算的毫秒数。

在JSR-310中,与java.util.Date等价的类是Instant,因此提供了方便的方法来进行相互转换:

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

java.util.Date 实例没有时区的概念。如果您在 java.util.Date 上调用 toString(),可能会感到奇怪,因为 toString() 是相对于时区的。然而,该方法实际上使用 Java 的默认时区来提供字符串。时区不是 java.util.Date 的实际状态的一部分。

Instant 也不包含任何关于时区的信息。因此,要从 Instant 转换为本地日期时间,需要指定一个时区。这可以是默认时区 - ZoneId.systemDefault() - 或者可以由应用程序控制的时区,例如用户首选项的时区。 LocalDateTime 具有方便的工厂方法,可以同时接受瞬时和时区:

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

在反向操作中,可以通过调用atZone(ZoneId)方法指定时区的LocalDateTime。然后可以将ZonedDateTime直接转换为Instant

LocalDateTime ldt = ...
ZonedDateTime zdt = ldt.atZone(ZoneId.systemDefault());
Date output = Date.from(zdt.toInstant());
请注意,从 LocalDateTime 转换为 ZonedDateTime 可能会引入意外的行为。这是因为由于夏令时,不是每个本地日期时间都存在。在秋季,本地时间线上存在重叠,同一本地日期时间会发生两次。在春季,有一个时间间隔,一个小时消失。请参阅atZone(ZoneId)的Javadoc以了解转换将执行的定义。
简而言之,如果你将一个java.util.Date 回转到一个 LocalDateTime,再返回一个 java.util.Date,由于夏令时的原因,你可能会得到不同的时间点。
额外信息:还有另一个差异会影响非常早期的日期。java.util.Date 使用一个在1582年10月15日改变的日历,该日期之前使用儒略历而不是公历。相比之下,java.time.* 用于所有时间的 ISO 日历系统(相当于公历)。在大多数用例中,ISO 日历系统是您想要的,但在比较1582年之前的日期时,您可能会看到奇怪的效果。

5
非常感谢您清晰的解释,特别是关于为什么 java.util.Date 不包含时区信息,但在 toString() 方法中打印它。正如您所发布的内容所示,即使是官方文档也没有清楚地说明这一点。 - Cherry
警告:LocalDateTime.ofInstant(date.toInstant()... 的行为并不像人们最初期望的那样。例如,使用这种方法 new Date(1111-1900,11-1,11,0,0,0); 将变成 1111-11-17 23:53:28。如果您需要在上一个示例中的结果为 1111-11-11 00:00:00,请查看 java.sql.Timestamp#toLocalDateTime() 的实现。 - dog
3
我已经添加了一个关于早期日期(1582年之前)的部分。顺便说一句,你提出的修复方法很可能是错误的,因为在java.util.Date中的1111-11-11实际上在历史上与java.time中的1111-11-18是同一天,这是由于不同的日历系统引起的(在1900年之前的许多时区存在6.5分钟的差异)。 - JodaStephen
3
值得注意的是,java.sql.Date#toInstant会抛出UnsupportedOperationException。因此,在java.sql.ResultSet#getDate上的RowMapper中不要使用toInstant - brass monkey

168

以下是我想出来的内容(和所有日期时间难题一样,它可能会因为一些奇怪的时区、闰年或夏令时调整而被证明是错误的 :D)

往返转换: Date <<->>LocalDateTime

假设有一个日期:Date date = [一些日期]

(1) LocalDateTime << Instant<< Date

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

(2) Date << Instant << LocalDateTime

(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) LocalDateTimeInstantDate更先进:

Date创建Instant

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

Instant 创建 Date(非必需,但为了说明):

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

Instant 创建 LocalDateTime

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

(2) Date << Instant << LocalDateTime

LocalDateTime创建Instant

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

Instant创建Date:

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

我认为这并不像你所想象的那样严重。使用java.util.Date的遗留代码从未依赖于java.time库,并且可能在没有它的情况下运行得非常好。对于许多商店来说,改变到新API的需求可能不是一个高优先级。新代码可以全新地使用新API。边缘情况是使用生成结果为“Date”的旧内部或第三方库的新代码,需要与使用java.time的新代码进行互操作。我认为这种情况很少见。 - scottb
2
@scottb,你是在问我还是那个提问者?至于我的想法,嗯,如果JDK 8 API中明确说明了转换,那将是很好的——他们至少可以这样做。无论如何,有很多库将被重构以包含新的Java-8功能,并且知道如何做这件事非常重要。 - The Coordinator
4
@scottb 为什么第三方案例不常见?其实很常见。例如:JDBC 4及以下版本(希望不包括5)。请问您需要翻译其他内容吗? - Raman
6
一般来说,在JSR-310中,不需要使用epoch-millis类型进行类型转换。有更好的替代方案,可以使用对象来实现,详见下文的完整回答。如果使用类似于UTC的区偏移量,上面的回答也只有在这种情况下才是完全有效的,对于像美国/纽约这样的完整时区,某些部分将无法正常工作。 - JodaStephen
2
不要使用 Instant.ofEpochMilli(date.getTime()),而是使用 date.toInstant() - goat
1
@goat toInstant() 看起来很不错,但是对于 java.sql.Date 它会失败,真是太糟糕了!所以最终更容易使用 Instant.ofEpochMilli(date.getTime()) - vadipp

29

如果您确定需要默认时区,则更方便的方法如下:

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

29
当然,这样做更容易,但我不喜欢将与jdbc相关的内容与简单的日期处理混在一起,至少就我的看法而言。 - Enrico Giurin
12
很抱歉,这个解决方案对于一个简单的问题来说太糟糕了。这就像将数字5定义为奥林匹克标志中环的数量一样荒谬。 - Madbreaks
@Madbreaks,你能否提供一个使用Java 7版本的解决方案? - H.Karatsanov

14

LocalDateTime转换为Date的最快方法是:

Date.from(ldt.toInstant(ZoneOffset.UTC))


8
以下内容似乎可以在将新的API LocalDateTime转换为java.util.date时起作用:
Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());

反向转换可以(希望如此)通过类似的方式实现...
希望能对您有所帮助...

8
这里有一切内容:http://blog.progs.be/542/date-to-java-time “往返转换”答案并不精确:当您进行操作时,可能会丢失一些细节。 如果您正在使用Java 8或更高版本,则应该使用新的日期时间API(java.time包)。
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);

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


只有在一年的秋季,当两个LocalDateTime重叠时的1小时内进行操作才会有问题。向前推进一个小时不会引起问题。大多数情况下,都可以正确地进行双向转换。 - David

6
如果您使用threetenbp并且在Android平台上,可以使用DateTimeUtils代替。

例如:

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

由于Date.from仅支持API 26+,因此您无法使用它。


4

我不确定这是否是最简单或最好的方法,也不确定是否存在任何缺陷,但它可以正常工作:

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时,一定存在陷阱。在夏令时转换期间,一个LocalDateTime可能不存在或出现两次。您需要确定每种情况下想要发生什么。 - Jon Skeet
1
顺便提一下,GregorianCalendar 属于旧的、笨重的 API,新的 java.time API 旨在取代它。 - Vadzim

0

我认为以下方法可以解决转换问题,而不考虑时区。如果有任何缺陷,请评论。

    LocalDateTime datetime //input
    public static final DateTimeFormatter yyyyMMddHHmmss_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    String formatDateTime = datetime.format(yyyyMMddHHmmss_DATE_FORMAT);
    Date outputDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(formatDateTime); //output

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