一尘不染

将java.util.Date转换为“ java.time”类型?

java

我有一个java.util.Date对象,或一个java.util.Calendar对象。如何在java.time框架中将其转换为正确的类型?

我听说我们现在应该使用java.time类型来做大部分业务逻辑。当使用尚未为java.time更新的旧代码时,我需要能够来回转换。什么类型映射到java.util.Datejava.util.Calendar


阅读 770

收藏
2020-03-09

共1个答案

一尘不染

避免使用旧的日期时间类

老式的日期时间类(包括java.util.Datejava.util.Calendar和)java.text.SimpleTextFormat已被证明设计不良,混乱且麻烦。尽可能避免它们。但是,当你必须与这些旧类型进行互操作时,可以在新旧之间进行转换。

请继续阅读,以获取基本的介绍(有些简化),以使你适应在新旧日期时间类之间来回移动。

java.time

该java.time框架由定义JSR 310,由大获成功的启发乔达时库,并通过扩展ThreeTen-EXTRA项目。的功能的大部分被后移植到Java 6和7在ThreeTen-反向移植项目,在为Android进一步适配ThreeTenABP项目。

哪些java.time类型匹配java.util.Date?好吧,一个java.util.Date对象基本上代表着UTC时间轴上的一个时刻,它是日期和时间的组合。我们可以将其转换为java.time中的几种类型中的任何一种。每个都在下面讨论。请注意,一些新方法已添加到旧的日期时间类中,以促进转换。

java.util.Date/.Calendar通过ZonedDateTime(或OffsetDateTime)转换为三种<code> Local…</ code>类型的示意图

Instant

java.time中的构造块是UTCInstant时间线上的一个时刻,分辨率为纳秒。

通常,你应该在UTC中执行大部分业务逻辑。在这样的工作中,Instant会经常使用。传递Instant对象,仅应用时区以向用户展示。当确实需要应用偏移量或时区时,请使用下面进一步介绍的类型。

java.util.DateInstant
鉴于Instant和java.util.Date都是UTC时间轴上的时刻,我们可以轻松地从java.util.Date移到Instant。旧的班级获得了一种新的方法java.util.Date::toInstant

Instant instant = myUtilDate.toInstant();

你可以去另一个方向,从一个Instant到java.util.Date。但是你可能会丢失有关小数秒的信息。Instant跟踪纳秒,最长到小数点后一位的9位,例如2016-01-23T12:34:56.123456789Z。java.util.Date和.Calendar均以毫秒为单位,最多到小数点后三位,例如2016-01-23T12:34:56.123Z。在此示例中,从Instant到Date表示的截断456789。

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

java.util.CalendarInstant
那用a java.util.Calendar代替a java.util.Date呢?在Calendar对象内部,日期时间以UTC()中自1970年第一时刻的纪元参考日期时间开始的毫秒数进行跟踪。因此,该值可以轻松转换为。1970-01-01T00:00:00.0ZInstant

Instant instant = myUtilCalendar.toInstant() ;

从java.util.GregorianCalendar到ZonedDateTime
更好的是,如果你的java.util.Calendar对象实际上是java.util.GregorianCalendar,则可以轻松地直接转到ZonedDateTime。这种方法的好处是保留嵌入的时区信息。

垂头丧气从接口的Calendar到具体类的GregorianCalendar。然后调用toZonedDateTime和from方法来回切换。

if (myUtilCalendar instanceof GregorianCalendar) {
    GregorianCalendar gregCal = (GregorianCalendar) myUtilCalendar; // Downcasting from the interface to the concrete class.
    ZonedDateTime zdt = gregCal.toZonedDateTime();  // Create `ZonedDateTime` with same time zone info found in the `GregorianCalendar`
}

往另一个方向…

java.util.Calendar myUtilCalendar = java.util.GregorianCalendar.from(zdt); // Produces an instant of `GregorianCalendar` which implements `Calendar` interface.

如上所述,请注意,你可能会丢失几分之一秒的信息。在纳秒在java.time类型(ZonedDateTime)被截断,以毫秒为单位的.Calendar/ .GregorianCalendar。

OffsetDateTime

从Instant我们可以应用的偏移从-UTC搬进挂钟时间对于一些局部性。偏移量是指UTC之前(向东)或UTC后面(向西)的小时数,甚至可能是数分钟和数秒。这个ZoneOffset班代表了这个想法。结果是一个OffsetDateTime对象。

ZoneOffset offset = ZoneOffset.of("-04:00"); 
OffsetDateTime odt = OffsetDateTime.ofInstant(instant, zoneOffset);

你可以去另一个方向,从一个OffsetDateTime到java.util.Date。提取一个Instant,然后按照上面的代码中的步骤进行操作。如上所述,任何纳秒都将被截断为毫秒(数据丢失)。

java.util.Date myUtilDate = java.util.Date.from(odt.toInstant());
ZonedDateTime

更好的是,应用一个完整的时区。时区是偏移量以及用于处理异常的规则,例如夏令时(DST)。

应用a ZoneId会得到一个ZonedDateTime对象。使用正确的时区名称(大陆/地区)。切勿使用3-4字母的缩写,例如EST或IST既不是标准化的也不是唯一的。

ZoneId zoneId = ZoneId.of("America/Montreal");
ZonedDateTime zdt = ZonedDateTime.ofInstant(instant, zoneId);

你可以去另一个方向,从一个ZonedDateTime到java.util.Date。提取一个Instant,然后按照上面的代码中的步骤进行操作。如上所述,任何纳秒都将被截断为毫秒(数据丢失)。

java.util.Date myUtilDate = java.util.Date.from( zdt.toInstant() );

而且我们在上面进一步看到了a ZonedDateTime可以转换为a GregorianCalendar。

LocalDate

有时,你可能需要一个仅包含日期的值,而没有日期和时区。为此,使用一个java.time.LocalDate对象。

请参阅此问题以获取更多讨论,将java.util.Date转换为java.time.LocalDate,尤其是由Joda-Time和java.time发明背后的主要人物写的这个答案。

关键是要经过一个ZonedDateTime(如上面的代码中生成的)。我们需要一个时区来确定日期。世界各地的日期各不相同,新的一天在东部更早开始。例如,在巴黎的午夜之后是新的一天,而在蒙特利尔仍然是“昨天”。因此,虽然a LocalDate不包含时区,但是需要一个时区来确定 a LocalDate。

LocalDate localDate = zdt.toLocalDate();

从另一个方向转换LocalDate为日期时间意味着发明一个时间。你可以选择在业务场景中有意义的任何时间。对于大多数人来说,一天的第一刻很有意义。你可能会很想在第一时间将其硬​​编码为时间00:00:00.0。在某些时区,由于夏时制(DST)或其他异常,该时间可能不是第一时间。因此,让java.time通过调用来确定正确的时间atStartOfDay。

ZonedDateTime zdt = localDate.atStartOfDay(zoneId);
LocalTime

在极少数情况下,你可能只需要一天中没有日期和时区的时间。这个概念由LocalTime班代表。如上所述LocalDate,LocalTime即使LocalTime对象不包含(不“记住”)该时区,我们也需要一个时区来确定。因此,同样,我们经历了一个ZonedDateTime从Instant上面获得的对象。

LocalTime localTime = zdt.toLocalTime();
LocalDateTime

与其他两种Local…类型一样,LocalDateTime没有分配时区或偏移量。因此,你可能很少使用它。它为你提供了日期时间的粗略概念,但不是时间轴上的一点。如果你是指可能会应用于某个时区的某个常规日期和某个时间,请使用此选项。

例如,“圣诞节从今年开始”将为2016-12-25T00:00:00.0。请注意,的文本表示形式中缺少任何偏移量或时区LocalDateTime。圣诞节在印度德里的开始要比在法国巴黎的开始快,后来在加拿大的魁北克省开始。应用这些区域的每个时区将在时间轴上产生不同的时刻。

LocalDateTime ldt = zdt.toLocalDateTime();
2020-03-09