1. Contexto histórico: cómo se trabajaba con fechas antes de Java 8
En la lejana antigüedad (antes de Java 8) para trabajar con fechas y horas se usaban las clases java.util.Date, java.util.Calendar y para el formateo — java.text.SimpleDateFormat. Era uno de esos casos en los que los programadores de todo el planeta suspiraban al unísono y, rechinando los dientes, escribían algo como:
import java.util.Date;
Date now = new Date();
System.out.println(now); // Mostrará algo extraño como "Wed Jun 05 14:15:22 MSK 2025"
Suena sencillo, pero en realidad no era tan bonito. He aquí solo algunas de las «alegrías» de la API antigua:
- Date — objeto mutable. Podías cambiarlo accidentalmente y esto a menudo llevaba a bugs.
- Los meses en Date y Calendar empezaban en cero (enero es 0, diciembre — 11), mientras que los días empezaban en uno.
- SimpleDateFormat no era seguro para hilos: si dos hilos formateaban una fecha al mismo tiempo, podías obtener resultados inesperados.
- Una enorme cantidad de métodos estaban marcados como @Deprecated (obsoletos), y el IDE te asustaba constantemente con advertencias amarillas.
- Trabajar con zonas horarias era un auténtico dolor: era fácil confundir la hora local con UTC, y mejor ni hablar del cambio a horario de verano/invierno.
Ejemplo del dolor
import java.util.Date;
Date date = new Date(2025, 5, 1); // año 2025, mes 5 (¿junio?), día 1
System.out.println(date); // ¡No es lo que esperas!
2. Aparición de java.time: un nuevo enfoque
Para 2014 quedó claro: la API antigua no solo era incómoda — era peligrosa. Por eso en Java apareció el nuevo paquete — java.time, que implementa la especificación JSR‑310. Esta API se inspiró en la popular biblioteca Joda-Time y enseguida se convirtió en el estándar de facto.
Paquetes y clases principales
- java.time — paquete principal donde viven todas las nuevas clases de fecha y hora.
- java.time.format — para formatear y parsear fechas y horas.
- java.time.temporal — para operaciones temporales más avanzadas.
- java.time.zone — para trabajar con zonas horarias.
Estos son los protagonistas principales de la nueva API:
| Clase | ¿Para qué? | Ejemplo de uso |
|---|---|---|
|
Solo fecha (año, mes, día) | Cumpleaños, sin hora |
|
Solo hora (horas, minutos, segundos) | Hora de una reunión, sin fecha |
|
Fecha y hora, sin zona horaria | Evento local |
|
Fecha y hora con zona horaria | Reunión en Minsk en hora local |
|
Instante absoluto (UTC) | Marca de evento en el registro |
|
Intervalo de tiempo (horas, minutos, seg.) | Duración de una llamada |
|
Periodo (años, meses, días) | Antigüedad laboral, edad |
Ejemplo: crear una fecha a la manera nueva
import java.time.LocalDate;
LocalDate today = LocalDate.now();
System.out.println(today); // Por ejemplo, "2025-06-05"
3. Ventajas de la nueva API
Inmutabilidad (immutable)
Todas las clases de java.time son inmutables. Esto significa: si creas un objeto LocalDate, no puedes modificarlo. Cualquier operación (por ejemplo, añadir un día) devuelve un objeto nuevo.
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
System.out.println(today); // 2025-06-05
System.out.println(tomorrow); // 2025-06-06
Gestión explícita de las zonas horarias
En la API antigua era fácil olvidar en qué zona horaria estaba la fecha. En java.time todo es explícito: si necesitas zona horaria — usa ZonedDateTime, si no — usa LocalDateTime.
import java.time.ZonedDateTime;
import java.time.ZoneId;
ZonedDateTime MinskTime = ZonedDateTime.now(ZoneId.of("Europe/Minsk"));
System.out.println(MinskTime); // 2025-06-05T14:23:45+03:00[Europe/Minsk]
Métodos prácticos para cálculos y comparación
LocalDate today = LocalDate.now();
LocalDate nextMonth = today.plusMonths(1);
boolean isAfter = LocalDate.now().plusDays(1).isAfter(today); // true
Formateo y parseo
El formateo y el parseo se hacen con DateTimeFormatter (lo veremos en detalle en la próxima lección):
import java.time.format.DateTimeFormatter;
LocalDate today = LocalDate.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");
String formatted = today.format(formatter); // "05.08.2025"
4. Compatibilidad: cómo convivir con el código antiguo
En el mundo real, muy a menudo hay que trabajar con bibliotecas o sistemas antiguos donde se usan Date y Calendar. Por suerte, la nueva API es amigable con el legado: se pueden convertir los tipos antiguos a los nuevos y viceversa.
Conversión Date ↔ Instant
import java.util.Date;
import java.time.Instant;
// Date → Instant
Date legacyDate = new Date();
Instant instant = legacyDate.toInstant();
// Instant → Date
Date dateBack = Date.from(instant);
Conversión Calendar ↔ ZonedDateTime
import java.util.Calendar;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.util.Date;
// Calendar → ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zdt = ZonedDateTime.ofInstant(
calendar.toInstant(),
calendar.getTimeZone().toZoneId()
);
// ZonedDateTime → Calendar
Calendar calBack = Calendar.getInstance();
calBack.setTime(Date.from(zdt.toInstant()));
Tabla: correspondencia entre clases antiguas y nuevas
| Clase antigua | Clase nueva | Comentario |
|---|---|---|
|
|
Tiempo absoluto |
|
|
Fecha y hora con zona horaria |
|
|
Formateo/parseo de fechas |
5. Práctica: primeros pasos con java.time
Supongamos que tienes un usuario con fecha de nacimiento. La guardaremos y la mostraremos:
import java.time.LocalDate;
public class UserProfile {
private String name;
private LocalDate birthDate;
public UserProfile(String name, LocalDate birthDate) {
this.name = name;
this.birthDate = birthDate;
}
public void printProfile() {
System.out.println("Nombre: " + name);
System.out.println("Fecha de nacimiento: " + birthDate);
}
}
public class Main {
public static void main(String[] args) {
UserProfile user = new UserProfile("Alisa", LocalDate.of(1998, 12, 25));
user.printProfile();
}
}
Salida:
Nombre: Alisa
Fecha de nacimiento: 1998-12-25
6. Comparación: API antigua vs API nueva
Ejemplo: añadir una semana a la fecha de nacimiento
Forma antigua (Date/Calendar):
import java.util.Calendar;
Calendar cal = Calendar.getInstance();
cal.set(1998, Calendar.DECEMBER, 25);
cal.add(Calendar.WEEK_OF_YEAR, 1);
System.out.println(cal.getTime()); // Engorroso y poco evidente
Forma nueva (java.time):
import java.time.LocalDate;
LocalDate birthDate = LocalDate.of(1998, 12, 25);
LocalDate nextWeek = birthDate.plusWeeks(1);
System.out.println(nextWeek); // 1999-01-01
Con la nueva API el código es más corto, más simple y más seguro.
7. Errores típicos al trabajar con java.time
Error n.º 1: olvidar que los objetos son inmutables.
Si llamas a date.plusDays(1); y no guardas el resultado, la fecha original se queda igual.
Error n.º 2: confundir LocalDate con LocalDateTime.
LocalDate almacena solo la fecha (año, mes, día), mientras que LocalDateTime también la hora. No los confundas si necesitas procesar horas y minutos.
Error n.º 3: usar clases antiguas en proyectos nuevos.
Si puedes — usa siempre java.time. Las clases antiguas solo para compatibilidad.
Error n.º 4: manejar mal las zonas horarias.
Si necesitas almacenar un evento importante para distintas regiones, utiliza ZonedDateTime o al menos Instant. ¡LocalDateTime no contiene información de zona horaria!
Error n.º 5: intentar comparar directamente LocalDate y LocalDateTime.
Son tipos de datos distintos; no se pueden comparar directamente. Primero conviértelos a un mismo tipo.
GO TO FULL VERSION