Java 8有一个全新的日期和时间API。其中最有用的类之一是LocalDateTime
,用于保存独立于时区的日期和时间。
使用旧的类java.util.Date
来处理日期和时间的代码行数可能有数百万行。因此,在旧代码和新代码之间进行接口交互时,需要进行两者之间的转换。由于似乎没有直接的方法可以完成这个任务,那么该如何实现呢?
Java 8有一个全新的日期和时间API。其中最有用的类之一是LocalDateTime
,用于保存独立于时区的日期和时间。
使用旧的类java.util.Date
来处理日期和时间的代码行数可能有数百万行。因此,在旧代码和新代码之间进行接口交互时,需要进行两者之间的转换。由于似乎没有直接的方法可以完成这个任务,那么该如何实现呢?
简短回答:
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年之前的日期时,您可能会看到奇怪的效果。java.util.Date
不包含时区信息,但在 toString()
方法中打印它。正如您所发布的内容所示,即使是官方文档也没有清楚地说明这一点。 - CherryLocalDateTime.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()
的实现。 - dogjava.sql.Date#toInstant
会抛出UnsupportedOperationException
。因此,在java.sql.ResultSet#getDate
上的RowMapper中不要使用toInstant
。 - brass monkey以下是我想出来的内容(和所有日期时间难题一样,它可能会因为一些奇怪的时区、闰年或夏令时调整而被证明是错误的 :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());
LocalDateTime
比Instant
比Date
更先进:从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);
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
Instant.ofEpochMilli(date.getTime())
,而是使用 date.toInstant()
。 - goattoInstant()
看起来很不错,但是对于 java.sql.Date
它会失败,真是太糟糕了!所以最终更容易使用 Instant.ofEpochMilli(date.getTime())
。 - vadipp如果您确定需要默认时区,则更方便的方法如下:
Date d = java.sql.Timestamp.valueOf( myLocalDateTime );
将LocalDateTime
转换为Date
的最快方法是:
Date.from(ldt.toInstant(ZoneOffset.UTC))
Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
如果您的系统时区不是UTC/GMT,则需要更改时间!
DateTimeUtils
代替。
例如:
Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
由于Date.from
仅支持API 26+,因此您无法使用它。
我不确定这是否是最简单或最好的方法,也不确定是否存在任何缺陷,但它可以正常工作:
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();
}
LocalDateTime
转换为Date
时,一定存在陷阱。在夏令时转换期间,一个LocalDateTime
可能不存在或出现两次。您需要确定每种情况下想要发生什么。 - Jon SkeetGregorianCalendar
属于旧的、笨重的 API,新的 java.time
API 旨在取代它。 - Vadzim我认为以下方法可以解决转换问题,而不考虑时区。如果有任何缺陷,请评论。
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