【发布时间】:2013-10-17 15:40:54
【问题描述】:
Java 8 有一个全新的日期和时间 API。此 API 中最有用的类之一是 LocalDateTime,用于保存与时区无关的日期与时间值。
可能有数百万行代码使用遗留类java.util.Date 用于此目的。因此,在连接新旧代码时,需要在两者之间进行转换。由于似乎没有直接的方法可以做到这一点,如何做到这一点?
【问题讨论】:
标签: java datetime java-8 java-time
Java 8 有一个全新的日期和时间 API。此 API 中最有用的类之一是 LocalDateTime,用于保存与时区无关的日期与时间值。
可能有数百万行代码使用遗留类java.util.Date 用于此目的。因此,在连接新旧代码时,需要在两者之间进行转换。由于似乎没有直接的方法可以做到这一点,如何做到这一点?
【问题讨论】:
标签: java datetime java-8 java-time
简答:
Date in = new Date();
LocalDateTime ldt = LocalDateTime.ofInstant(in.toInstant(), ZoneId.systemDefault());
Date out = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
说明:
(基于this question约LocalDate)
尽管有它的名字,java.util.Date 代表时间线上的一个瞬间,而不是“日期”。对象中存储的实际数据是自 1970-01-01T00:00Z(格林威治标准时间/UTC 1970 开始时的午夜)以来的long 毫秒计数。
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());
反过来,LocalDateTime 时区是通过调用atZone(ZoneId) 方法来指定的。然后可以将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() 期间打印。事件official documentation 在您发布时并没有清楚地说明这一点。
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() 的实现。
java.sql.Date#toInstant 会抛出一个UnsupportedOperationException。所以不要在java.sql.ResultSet#getDate 上的RowMapper 中使用toInstant。
一切都在这里:http://blog.progs.be/542/date-to-java-time
“往返”的答案并不准确:当你这样做时
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
如果您的系统时区不是 UTC/GMT,请更改时间!
【讨论】:
这是我想出的(和所有日期时间难题一样,它可能会因一些奇怪的时区-闰年-日光调整而被推翻:D)
Date > LocalDateTime
给定:Date date = [some date]
(1)LocalDateTimeInstantDate
Instant instant = Instant.ofEpochMilli(date.getTime());
LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneOffset.UTC);
(2)DateInstantLocalDateTime
Instant instant = ldt.toInstant(ZoneOffset.UTC);
Date date = Date.from(instant);
例子:
Date date = new Date();
System.out.println(date + " long: " + date.getTime());
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);
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()
toInstant() 看起来不错,除了 java.sql.Date 失败,arggggh!所以终于用Instant.ofEpochMilli(date.getTime())更方便了。
如果您确定需要默认时区,则更方便:
Date d = java.sql.Timestamp.valueOf( myLocalDateTime );
【讨论】:
我不确定这是否是最简单或最好的方法,或者是否有任何陷阱,但它有效:
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 可能不存在,或出现两次。你需要弄清楚在每种情况下你想发生什么。
GregorianCalendar 属于旧的尴尬 API,新的 java.time API 旨在取代
LocalDateTime -> Date 最快的方法是:
Date.from(ldt.toInstant(ZoneOffset.UTC))
【讨论】:
如果您在 Android 上使用 threetenbp,则可以改用 DateTimeUtils。
例如:
Date date = DateTimeUtils.toDate(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
你不能使用Date.from,因为它只支持 api 26+
【讨论】:
从新 API LocalDateTime 转换为 java.util.date 时,以下似乎有效:
Date.from(ZonedDateTime.of({time as LocalDateTime}, ZoneId.systemDefault()).toInstant());
可以(希望)以类似的方式实现反向转换...
希望对你有帮助……
【讨论】:
我认为以下方法将在不考虑时区的情况下解决转换问题。 如有不足请评论。
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
【讨论】: