Conversión entre java.time.LocalDateTime y java.util.Date

485

Java 8 tiene una API completamente nueva para fecha y hora. Una de las clases más útiles en esta API es LocalDateTime, para mantener un valor de fecha con hora independiente de la zona horaria.

Probablemente hay millones de líneas de código que usan la clase heredada java.util.Datepara este propósito. Como tal, cuando se interconecta el código antiguo y el nuevo, será necesario convertir entre los dos. Como parece que no hay métodos directos para lograr esto, ¿cómo se puede hacer?

Knut Arne Vedaa
fuente

Respuestas:

706

Respuesta corta:

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

Explicación: (basado en esta pregunta sobre LocalDate)

A pesar de su nombre, java.util.Daterepresenta un instante en la línea de tiempo, no una "fecha". Los datos reales almacenados dentro del objeto son un longrecuento de milisegundos desde 1970-01-01T00: 00Z (medianoche al comienzo de 1970 GMT / UTC).

La clase equivalente a java.util.Dateen JSR-310 es Instant, por lo tanto, hay métodos convenientes para proporcionar la conversión de aquí para allá:

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

Una java.util.Dateinstancia no tiene concepto de zona horaria. Esto puede parecer extraño si llama toString()a un java.util.Date, porque toStringes relativo a una zona horaria. Sin embargo, ese método en realidad usa la zona horaria predeterminada de Java sobre la marcha para proporcionar la cadena. La zona horaria no es parte del estado real de java.util.Date.

An Instanttampoco contiene ninguna información sobre la zona horaria. Por lo tanto, para convertir Instantuna fecha y hora local en una hora local, es necesario especificar una zona horaria. Esta podría ser la zona predeterminada ZoneId.systemDefault(), o podría ser una zona horaria que controla su aplicación, como una zona horaria de las preferencias del usuario. LocalDateTimetiene un método de fábrica conveniente que toma tanto el instante como la zona horaria:

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

A la inversa, la LocalDateTimezona horaria se especifica llamando al atZone(ZoneId)método. La ZonedDateTimecontinuación, se puede convertir directamente a un Instant:

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

Tenga en cuenta que la conversión de LocalDateTimea ZonedDateTimetiene el potencial de introducir un comportamiento inesperado. Esto se debe a que no todas las fechas y horas locales existen debido al horario de verano. En otoño / otoño, hay una superposición en la línea de tiempo local donde la misma fecha y hora local ocurre dos veces. En primavera, hay una brecha, donde desaparece una hora. Consulte el Javadoc de atZone(ZoneId)para obtener más información sobre lo que hará la conversión.

Resumen, si una de ida y vuelta java.util.Datea una LocalDateTimey de nuevo a una java.util.Dateque puede terminar con una instantánea diferente debido al horario de verano.

Información adicional: hay otra diferencia que afectará las fechas muy antiguas. java.util.Dateusa un calendario que cambia el 15 de octubre de 1582, con fechas anteriores al calendario juliano en lugar del gregoriano. Por el contrario, java.time.*utiliza el sistema de calendario ISO (equivalente al gregoriano) de todos los tiempos. En la mayoría de los casos de uso, el sistema de calendario ISO es lo que desea, pero puede ver efectos extraños al comparar fechas anteriores al año 1582.

JodaStephen
fuente
55
Muchas gracias por una explicación clara. Especialmente por qué java.util.Dateno contiene zona horaria, pero imprímala durante toString(). La documentación oficial del evento no dice esto claramente cuando publicas.
Cereza
Advertencia: LocalDateTime.ofInstant(date.toInstant()... no se comporta como uno esperaría ingenuamente. Por ejemplo, new Date(1111-1900,11-1,11,0,0,0);se convertirá en 1111-11-17 23:53:28utilizar este enfoque. Eche un vistazo a la implementación de java.sql.Timestamp#toLocalDateTime()si necesita que el resultado esté 1111-11-11 00:00:00en el ejemplo anterior.
perro
2
He agregado una sección sobre fechas muy antiguas (anteriores a 1582). FWIW, su solución sugerida puede estar equivocada, ya que 1111-11-11 en java.util.Date es el mismo día real en la historia que 1111-11-18 en java.time debido a los diferentes sistemas de calendario (la diferencia de 6.5 minutos ocurre en muchas zonas horarias antes de 1900)
JodaStephen
2
También vale la pena señalar que java.sql.Date#toInstantarroja un UnsupportedOperationException. Así que no lo uses toInstanten un RowMapper java.sql.ResultSet#getDate.
LazerBass
132

Esto es lo que se me ocurrió (y, como todos los acertijos de fecha y hora, probablemente será refutado en función de algún ajuste extraño de la zona horaria, el día de verano y el día: D)

Disparo redondo: Date<<->>LocalDateTime

Dado: 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);

Ejemplo:

Dado:

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

(1) LocalDateTime<< Instant<< Date:

Crear Instantdesde Date:

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

Crear Datedesde Instant(no es necesario, pero para ilustración):

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

Crear LocalDateTimedesdeInstant

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

(2) Date<< Instant<<LocalDateTime

Crear Instantdesde LocalDateTime:

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

Crear Datedesde Instant:

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

El resultado es:

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
Saint Hill
fuente
2
@scottb, ¿me diriges eso a mí o a la persona que hizo la pregunta? En cuanto a mis pensamientos, bueno, sería bueno tener la conversión declarada explícitamente en la API jdk 8: al menos podrían haberlo hecho. En cualquier caso, hay muchas bibliotecas que serán refactorizadas para incluir las nuevas características de Java-8 y saber cómo hacerlo es importante.
El coordinador el
44
@scottb ¿Por qué el caso de terceros sería poco común? Es muy común. Un ejemplo: JDBC 4 y menos (con suerte no 5).
Raman
55
Como regla general de JSR-310, no hay necesidad de convertir entre tipos usando epoch-millis. Existen mejores alternativas usando objetos, vea mi respuesta completa a continuación. La respuesta anterior también solo es completamente válida si se utiliza un desplazamiento de zona como UTC; algunas partes de la respuesta no funcionarán para una zona horaria completa como America / New_York.
JodaStephen
2
En lugar de Instant.ofEpochMilli(date.getTime())hacerlodate.toInstant()
cabra
1
@goat se toInstant()ve bien, excepto que falla java.sql.Date, ¡arggggh! Entonces, finalmente es más fácil de usar Instant.ofEpochMilli(date.getTime()).
vadipp
22

Una forma mucho más conveniente si está seguro de que necesita una zona horaria predeterminada:

Date d = java.sql.Timestamp.valueOf( myLocalDateTime );
Petrychenko
fuente
25
Claro, es más fácil, pero no me gusta mezclar cosas relacionadas con jdbc con un simple manejo de fechas, al menos en mi humilde opinión.
Enrico Giurin
99
Lo siento, esta es una solución terrible para un problema simple. Es como definir 5 para que sea igual al número de anillos en el logotipo olímpico.
Madbreaks
7

lo siguiente parece funcionar cuando se convierte de una nueva API LocalDateTime a java.util.date:

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

la conversión inversa se puede lograr (con suerte) de manera similar ...

Espero eso ayude...

mareado
fuente
5

Todo esta aquí: http://blog.progs.be/542/date-to-java-time

La respuesta con "ida y vuelta" no es exacta: cuando lo haces

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

si la zona horaria de su sistema no es UTC / GMT, ¡cambie la hora!

joe-mojo
fuente
Solo si lo hace durante 1 hora al año, en el otoño, cuando se superponen dos veces desde LocalDateTime. El avance de la primavera no causa problemas. La mayoría de las veces convertirá ambas direcciones correctamente.
David
4

La forma más rápida para LocalDateTime-> Datees:

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

Lukasz Czerwinski
fuente
3

No estoy seguro de si esta es la manera más simple o mejor, o si hay algún inconveniente, pero funciona:

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();
}
Knut Arne Vedaa
fuente
3
Definitivamente hay un escollo que va de LocalDateTimea Date. En las transiciones de horario de verano, a LocalDateTimepuede ser inexistente o ocurrir dos veces. Necesita resolver lo que quiere que suceda en cada caso.
Jon Skeet
1
Por cierto, GregorianCalendarpertenece a la antigua API incómoda que la nueva java.timeAPI tiene como objetivo reemplazar
Vadzim
3

Si está en Android y usa threetenbp , puede usarDateTimeUtils en lugar.

ex:

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

no puede usar Date.fromya que solo es compatible con la API 26+

humillado
fuente