El paquete java.time llegó en Java 8 y sustituyó a Date y Calendar, que eran mutables y empezaban los meses en cero. Si estás escribiendo código nuevo con SimpleDateFormat, esto es lo que lo reemplaza.
También te puede interesar
Cuatro clases, y elegir bien
| Clase | Guarda | Úsala para |
|---|---|---|
LocalDate |
2026-10-06 | cumpleaños, vencimientos, un día sin hora |
LocalTime |
14:30 | una hora del día, sin fecha |
LocalDateTime |
las dos, sin zona | la hora de una reunión local |
ZonedDateTime |
las dos con zona | cualquier cosa que cruce husos |
Instant |
un punto en la línea de tiempo | marcas de tiempo, guardar en base de datos |
salidaLocalDate 2026-10-06 LocalTime 14:30 LocalDateTime 2026-10-06T14:30 ZonedDateTime 2026-10-06T14:30+02:00[Europe/Madrid] Instant 2026-10-06T12:30:00Z
Fíjate en la última línea: el mismo momento, visto desde UTC, son las 12:30. Un LocalDateTime no identifica un instante: «el 6 de octubre a las 14:30» ocurre a horas distintas en Madrid y en Tokio. Para registrar cuándo pasó algo, Instant.
Son inmutables
LocalDate manana = d.plusDays(1); d.plusMonths(3); d.withDayOfMonth(1); d.withDayOfMonth(d.lengthOfMonth());
salidad=2026-10-06 d.plusDays(1)=2026-10-07 dentro de 3 meses: 2027-01-06 primer día del mes: 2026-10-01 último del mes : 2026-10-31
Ninguna operación modifica el objeto: todas devuelven uno nuevo. Igual que con los String, hay que recoger el resultado. Esto, que parece un detalle, es la razón principal de que java.time sustituyera a Calendar: una fecha que cambia bajo tus pies es un error difícil de encontrar.
Diferencias: Period y Duration
ChronoUnit.DAYS.between(d, otra); // 146 Period.between(d, otra); // 0 años, 4 meses, 23 días Duration.ofMinutes(195); // PT3H15M
salidadías entre: 146 Period : 0a 4m 23d Duration : PT3H15M -> 3h 15min
La distinción importa: Period es para fechas —años, meses, días, que duran lo que duran según el calendario— y Duration es para tiempo exacto, en segundos y nanosegundos. Sumar «un mes» no es sumar 30 días, y sumar «un día» en el cambio de hora no es sumar 24 horas. Por eso son dos clases.
Formatear y analizar
var fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
dt.format(fmt);
LocalDateTime.parse("06/10/2026 14:30", fmt);
d.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(Locale.of("es","ES")));
salida06/10/2026 14:30 2026-10-06T14:30 ISO: 2026-10-06T14:30:00 largo: martes, 6 de octubre de 2026
MM es el mes y mm los minutos; yyyy es el año y YYYY es el «año de la semana», que en los últimos días de diciembre devuelve el año siguiente. Ese último ha roto sistemas en producción cada 31 de diciembre durante años.Y lo que pasa con una fecha imposible, que es lo que uno quiere que pase:
LocalDate.parse("2026-02-30")Text '2026-02-30' could not be parsed: Invalid date 'FEBRUARY 30'
Zonas horarias
madrid.withZoneSameInstant(ZoneId.of("America/Mexico_City"));
madrid.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
salidaMadrid 2026-10-06T14:30+02:00[Europe/Madrid] Ciudad de México 2026-10-06T06:30-06:00[America/Mexico_City] Tokio 2026-10-06T21:30+09:00[Asia/Tokyo]
El mismo instante, tres relojes. withZoneSameInstant cambia la zona conservando el momento; withZoneSameLocal hace lo contrario, conserva los números y cambia el instante. Casi siempre quieres el primero.
Y usa siempre identificadores de región —Europe/Madrid— y no desplazamientos fijos como +02:00: la región sabe cuándo hay horario de verano y el desplazamiento no.
Resumen
| Quieres… | Usa |
|---|---|
| Hoy | LocalDate.now() |
| El instante actual, para registrar | Instant.now() |
| Sumar días o meses | plusDays, plusMonths (devuelven otro) |
| Cuántos días hay entre dos | ChronoUnit.DAYS.between(a, b) |
| Años, meses y días | Period.between(a, b) |
| Horas y minutos | Duration |
| Mostrar con formato | DateTimeFormatter.ofPattern |
| Cambiar de huso | withZoneSameInstant |
Todo el código se compiló y se ejecutó con Java 25 LTS en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.