W3docs

Clase Date Heredada de Java

La clase java.util.Date heredada: por qué fue reemplazada por java.time y cómo convertir entre ambas.

java.util.Date fue la clase original de fecha y hora de Java, presente desde Java 1.0 en 1995. Sigue en el JDK; el código nuevo no debería usarla, pero la encontrarás en bibliotecas, bases de datos (java.sql.Date la extiende) y en cualquier código anterior a 2014 aproximadamente. Este capítulo existe para conectarla con java.time.

La versión corta: Date es internamente un envoltorio alrededor de un long de milisegundos desde el epoch, muy parecido a como Instant es internamente un par (seconds, nanos). Por lo tanto, la conversión natural es Date ↔ Instant. Para cualquier otra cosa (año, mes, día, aritmética de calendario), convierte primero a java.time.

Qué es realmente Date

public class Date implements Cloneable, Comparable<Date>, Serializable {
  private long fastTime;        // milliseconds since 1970-01-01T00:00:00Z
}

Ese es todo el estado real. Un Date es un punto en el tiempo, medido en milisegundos desde el Unix epoch, en UTC. A pesar del nombre, no lleva una fecha de calendario — getYear() y sus equivalentes calculan a partir del valor en milisegundos en la zona horaria predeterminada de la JVM, que es el origen de los famosos problemas de esta API.

Date now = new Date();                                       // current moment
Date epoch = new Date(0);                                    // 1970-01-01T00:00:00Z
Date fromMs = new Date(1_700_000_000_000L);

long ms = now.getTime();                                     // milliseconds since epoch

new Date() y Date.getTime() son los dos métodos que han envejecido bien. Todo lo demás ha sido deprecado o contiene trampas peligrosas.

Los accesores de calendario deprecados

Estos métodos fueron deprecados en Java 1.1 (1997) cuando se añadió Calendar:

date.getYear();                                              // year - 1900   (deprecated)
date.getMonth();                                             // 0-11          (deprecated)
date.getDate();                                              // 1-31, day of month (deprecated)
date.getDay();                                               // 0-6, day of week (deprecated)
date.getHours(); date.getMinutes(); date.getSeconds();       // local-zone reads (deprecated)

Las deprecaciones llevan 28 años ahí. Siguen funcionando. Las trampas:

  • getYear() devuelve year - 1900. Para 2025, devuelve 125. Es candidato indiscutible a "decisión de API más extraña del JDK".
  • getMonth() devuelve 0-11. Enero es 0, diciembre es 11. Los errores de desfase por uno están garantizados si escribes getMonth() + 1 y lo olvidas alguna vez.
  • Cada accesor lee en la zona horaria predeterminada de la JVM. El mismo Date en dos máquinas en zonas distintas da resultados distintos para getDate().

No llames a estos métodos. En el momento en que te encuentres queriendo usar date.getYear(), conviértelo a Instant/ZonedDateTime y usa los accesores modernos.

El puente Date ↔ Instant

Date legacy = new Date();
Instant inst = legacy.toInstant();                            // since Java 8

Instant other = Instant.parse("2025-11-04T19:30:00Z");
Date back = Date.from(other);                                 // since Java 8

toInstant() y Date.from(...) son los métodos de conversión modernos, añadidos con java.time. Son las únicas dos llamadas a java.util.Date que deberías escribir en código nuevo.

La conversión pierde información en una dirección: Date tiene precisión de milisegundos, Instant tiene precisión de nanosegundos. El viaje de ida y vuelta Instant → Date → Instant trunca los nanos por debajo del milisegundo:

Instant high = Instant.parse("2025-11-04T19:30:00.123456789Z");
Instant low = Date.from(high).toInstant();
// low = 2025-11-04T19:30:00.123Z — the 456789 nanos are gone

Para marcas de tiempo de servidor esto está bien; para captura de eventos de alta resolución, mantente en Instant de extremo a extremo.

java.sql.Date y java.sql.Timestamp

Los tipos JDBC son subclases de java.util.Date:

  • java.sql.Date — una fecha sin hora (el componente de tiempo se fuerza a 00:00:00 en alguna zona). Nombre engañoso; sigue siendo un envoltorio de milisegundos desde el epoch por debajo.
  • java.sql.Time — una hora sin fecha.
  • java.sql.Timestamp — como Date pero con precisión de nanosegundos.

Todas tienen métodos de conversión de JDK 8:

java.sql.Date    sqlDate  = java.sql.Date.valueOf(LocalDate.of(2025, 11, 4));
LocalDate localDate = sqlDate.toLocalDate();

java.sql.Timestamp ts = java.sql.Timestamp.from(Instant.now());
Instant inst = ts.toInstant();

Los drivers JDBC modernos también aceptan tipos java.time directamente mediante setObject/getObject — para código nuevo, omite los tipos java.sql.* y usa LocalDate/Instant. Las conversiones están ahí para el código que debe interoperar con un driver o framework que aún no ha migrado.

Comparación y ordenación

Date implementa Comparable<Date>. El orden es por milisegundos desde el epoch — igual que Instant. Por lo tanto, ordenar List<Date> funciona igual que ordenar List<Instant>.

equals compara el long subyacente. Dos valores Date son iguales si y solo si tienen el mismo valor en milisegundos. El hashing funciona (es (int)(time ^ (time >>> 32))), así que Date está bien como clave de HashMap — aunque, de nuevo, Instant es la elección moderna.

Mutabilidad

La trampa oculta más importante: Date es mutable.

Date d = new Date();
d.setTime(0);                                                // mutates d in place

Eso significa que cualquier método que acepte o devuelva un Date es peligroso — el llamador puede cambiar el valor después de pasarlo; el receptor puede cambiar el valor que el llamador tiene. El código de biblioteca copia defensivamente (new Date(d.getTime())) cada Date que recibe. Este es el tipo de gestión que java.time eliminó completamente al hacer cada tipo inmutable.

En código heredado, trata cualquier campo Date como si pudiera cambiar por debajo de ti. Conviértelo a Instant en el límite de la API si necesitas una instantánea estable.

SimpleDateFormat: lo otro deprecado

Junto a Date en código antiguo está java.text.SimpleDateFormat:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String s = sdf.format(new Date());
Date d = sdf.parse("2025-11-04");

SimpleDateFormat no es seguro para hilos. Compartirlo entre hilos producirá salidas incorrectas, excepciones, o ambas, de forma intermitente. La solución estándar en código heredado es ThreadLocal<SimpleDateFormat>; el reemplazo moderno es DateTimeFormatter, que es seguro para hilos y cacheable.

Si mantienes código con un static SimpleDateFormat, trátalo como un bug conocido independientemente de si alguien ha reportado un fallo todavía.

Un ejemplo funcional: interoperabilidad con código heredado

El programa a continuación usa Date tal como lo encontrarías en una API heredada y muestra la ruta de conversión a java.time para cada operación. Léelo como "aquí está la receta de migración": cada llamada heredada tiene un equivalente de una línea con Instant o ZonedDateTime.

java— editable, runs on the server

Lo que hay que extraer de la ejecución:

  • Date.toString() se imprime en la zona horaria predeterminada de la JVM. El mismo valor Date se muestra de forma diferente en un servidor UTC que en un portátil de America/New_York. Ese es el defecto de diseño central que impulsó el rediseño de java.time — el valor está en UTC, la visualización es local, y la API no ofrece una forma sencilla de distinguir qué vista estás mirando. Si te importa qué zona quieres decir, convierte a ZonedDateTime y proporciona la zona explícitamente.
  • now.getYear() devolvió 125 para 2025. La convención year - 1900 fue un error de Java 1.0 que nunca se corrigió; el método fue deprecado en 1.1 y sigue ahí. Cualquier llamada a getYear() en un Date que lea "125" o "85" es un bug esperando mostrarse a un usuario.
  • Instant.parse("...nanoseconds") imprimió nueve dígitos de precisión; el mismo valor pasado por Date.from y de vuelta perdió los últimos seis. Para registros de servidor (la precisión de milisegundos es suficiente), el truncamiento no importa. Para "capturé este evento con temporización de alta precisión", no hagas el viaje de ida y vuelta por Date.
  • El +1 día heredado era now.getTime() + 24L * 60 * 60 * 1000 — aritmética manual, fácil de equivocarse (olvidar la L y desbordarse). El moderno inst.plus(Duration.ofDays(1)) es seguro de tipos y se lee en voz alta. Cuando migras, reemplazar cada cálculo time + N * ms con Duration es la fruta más fácil de cosechar.
  • La demostración de mutabilidad al final mostró que shared.setTime(0) cambiaba el valor visto a través de ambas referencias. En una base de código multihilo eso es una condición de carrera; en código de un solo hilo sigue siendo un ritual de copia defensiva que el JDK impuso a cada biblioteca. La API moderna nunca pide ese ritual.

Qué sigue

java.util.Date es la mitad de la API heredada. La otra mitad es java.util.Calendar — la clase añadida en Java 1.1 para dar a Date los accesores de calendario que Date en sí mismo no debería tener. El siguiente capítulo, Java Calendar Class, es el último de esta parte y lo cubre con la misma forma de receta de migración: cada llamada heredada tiene un reemplazo en java.time.

Práctica

Práctica
Una biblioteca antigua devuelve `java.util.Date` desde `getCreatedAt()`. Quieres saber '¿está esto en el mismo día de calendario que hoy en Nueva York?'. ¿Cuál es el camino correcto?
Una biblioteca antigua devuelve `java.util.Date` desde `getCreatedAt()`. Quieres saber '¿está esto en el mismo día de calendario que hoy en Nueva York?'. ¿Cuál es el camino correcto?
Was this page helpful?