¿Cuál es la mejor manera de convertir un objeto java.util.Date
al nuevo JDK 8/JSR-310 java.time.LocalDate
?
Date input = new Date();
LocalDate date = ???
Respuesta corta
Date input = new Date();
LocalDate date = input.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
**Explicación.
A pesar de su nombre, java.util.Date
representa un instante en la línea de tiempo, no una "fecha". Los datos reales almacenados en el objeto son un recuento long
de milisegundos desde 1970-01-01T00:00Z (medianoche del inicio de 1970 GMT/UTC).
La clase equivalente a java.util.Date
en JSR-310 es Instant
, por lo que hay un método conveniente toInstant()
para proporcionar la conversión:
Date input = new Date();
Instant instant = input.toInstant();
Una instancia de java.util.Date
no tiene el concepto de zona horaria. Esto puede parecer extraño si se llama a toString()
en un java.util.Date
, porque el toString
es relativo a una zona horaria. Sin embargo, este método utiliza la zona horaria por defecto de Java sobre la marcha para proporcionar la cadena. La zona horaria no forma parte del estado actual de java.util.Date
.
Un Instant
tampoco contiene ninguna información sobre la zona horaria. Por lo tanto, para convertir de un Instant
a una fecha local es necesario especificar una zona horaria. Esta puede ser la zona por defecto - ZoneId.systemDefault()
- o puede ser una zona horaria que su aplicación controle, como una zona horaria de las preferencias del usuario. Utilice el método atZone()
para aplicar la zona horaria:
Date input = new Date();
Instant instant = input.toInstant();
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());
Un ZonedDateTime
contiene un estado que consiste en la fecha y la hora local, la zona horaria y el desplazamiento de GMT/UTC. Como tal, la fecha - LocalDate
- puede ser fácilmente extraída usando toLocalDate()
:
Date input = new Date();
Instant instant = input.toInstant();
ZonedDateTime zdt = instant.atZone(ZoneId.systemDefault());
LocalDate date = zdt.toLocalDate();
Respuesta de Java 9
En Java SE 9, se ha añadido un nuevo método que simplifica ligeramente esta tarea:
Date input = new Date();
LocalDate date = LocalDate.ofInstant(input.toInstant(), ZoneId.systemDefault());
Esta nueva alternativa es más directa, creando menos basura, y por lo tanto debería funcionar mejor.
La mejor manera es:
Date date = ...;
Instant.ofEpochMilli(date.getTime()).atZone(ZoneId.systemDefault()).toLocalDate()
Ventajas de esta versión:
Funciona independientemente de que la entrada sea una instancia de java.util.Date
o su subclase java.sql.Date
(a diferencia de lo que hace @JodaStephen's). Esto es común con los datos originados por JDBC. java.sql.Date.toInstant()` siempre lanza una excepción.
es lo mismo para JDK8 y JDK7 con el backport JSR-310
Personalmente uso una clase de utilidad (pero esto no es compatible con el backport):
/**
* Utilities for conversion between the old and new JDK date types
* (between {@code java.util.Date} and {@code java.time.*}).
*
* <p>
* All methods are null-safe.
*/
public class DateConvertUtils {
/**
* Calls {@link #asLocalDate(Date, ZoneId)} with the system default time zone.
*/
public static LocalDate asLocalDate(java.util.Date date) {
return asLocalDate(date, ZoneId.systemDefault());
}
/**
* Creates {@link LocalDate} from {@code java.util.Date} or it's subclasses. Null-safe.
*/
public static LocalDate asLocalDate(java.util.Date date, ZoneId zone) {
if (date == null)
return null;
if (date instanceof java.sql.Date)
return ((java.sql.Date) date).toLocalDate();
else
return Instant.ofEpochMilli(date.getTime()).atZone(zone).toLocalDate();
}
/**
* Calls {@link #asLocalDateTime(Date, ZoneId)} with the system default time zone.
*/
public static LocalDateTime asLocalDateTime(java.util.Date date) {
return asLocalDateTime(date, ZoneId.systemDefault());
}
/**
* Creates {@link LocalDateTime} from {@code java.util.Date} or it's subclasses. Null-safe.
*/
public static LocalDateTime asLocalDateTime(java.util.Date date, ZoneId zone) {
if (date == null)
return null;
if (date instanceof java.sql.Timestamp)
return ((java.sql.Timestamp) date).toLocalDateTime();
else
return Instant.ofEpochMilli(date.getTime()).atZone(zone).toLocalDateTime();
}
/**
* Calls {@link #asUtilDate(Object, ZoneId)} with the system default time zone.
*/
public static java.util.Date asUtilDate(Object date) {
return asUtilDate(date, ZoneId.systemDefault());
}
/**
* Creates a {@link java.util.Date} from various date objects. Is null-safe. Currently supports:<ul>
* <li>{@link java.util.Date}
* <li>{@link java.sql.Date}
* <li>{@link java.sql.Timestamp}
* <li>{@link java.time.LocalDate}
* <li>{@link java.time.LocalDateTime}
* <li>{@link java.time.ZonedDateTime}
* <li>{@link java.time.Instant}
* </ul>
*
* @param zone Time zone, used only if the input object is LocalDate or LocalDateTime.
*
* @return {@link java.util.Date} (exactly this class, not a subclass, such as java.sql.Date)
*/
public static java.util.Date asUtilDate(Object date, ZoneId zone) {
if (date == null)
return null;
if (date instanceof java.sql.Date || date instanceof java.sql.Timestamp)
return new java.util.Date(((java.util.Date) date).getTime());
if (date instanceof java.util.Date)
return (java.util.Date) date;
if (date instanceof LocalDate)
return java.util.Date.from(((LocalDate) date).atStartOfDay(zone).toInstant());
if (date instanceof LocalDateTime)
return java.util.Date.from(((LocalDateTime) date).atZone(zone).toInstant());
if (date instanceof ZonedDateTime)
return java.util.Date.from(((ZonedDateTime) date).toInstant());
if (date instanceof Instant)
return java.util.Date.from((Instant) date);
throw new UnsupportedOperationException("Don't know hot to convert " + date.getClass().getName() + " to java.util.Date");
}
/**
* Creates an {@link Instant} from {@code java.util.Date} or it's subclasses. Null-safe.
*/
public static Instant asInstant(Date date) {
if (date == null)
return null;
else
return Instant.ofEpochMilli(date.getTime());
}
/**
* Calls {@link #asZonedDateTime(Date, ZoneId)} with the system default time zone.
*/
public static ZonedDateTime asZonedDateTime(Date date) {
return asZonedDateTime(date, ZoneId.systemDefault());
}
/**
* Creates {@link ZonedDateTime} from {@code java.util.Date} or it's subclasses. Null-safe.
*/
public static ZonedDateTime asZonedDateTime(Date date, ZoneId zone) {
if (date == null)
return null;
else
return asInstant(date).atZone(zone);
}
}
El método asLocalDate()
es a prueba de nulos, utiliza toLocalDate()
, si la entrada es java.sql.Date
(puede ser anulado por el controlador JDBC para evitar problemas de zona horaria o cálculos innecesarios), de lo contrario utiliza el método antes mencionado.
Si estás usando Java 8, la respuesta de @JodaStephen'es obviamente la mejor. Sin embargo, si estás trabajando con el JSR-310 backport, lamentablemente tienes que hacer algo como esto:
Date input = new Date();
Calendar cal = Calendar.getInstance();
cal.setTime(input);
LocalDate date = LocalDate.of(cal.get(Calendar.YEAR),
cal.get(Calendar.MONTH) + 1,
cal.get(Calendar.DAY_OF_MONTH));