Saltar al contenido
fechas.java · devschool

Fechas y horas en Java con java.time

Lección 24 de 26 · 10 min de lectura · Actualizado el

En esta lección
  1. Las clases principales
  2. Crear fechas y horas
  3. Sumar y restar tiempo
  4. Comparar fechas
  5. Diferencia entre fechas: Period, Duration y ChronoUnit
  6. Formatear fechas con DateTimeFormatter
  7. Días de la semana: DayOfWeek
  8. Ejemplo: calcular la edad
  9. Zonas horarias: ZonedDateTime
  10. Por qué no Date ni Calendar
  11. Errores frecuentes
  12. Resumen

Casi cualquier aplicación trabaja con fechas: el día de un pedido, la hora de una cita en el médico, la fecha de nacimiento de un socio del gimnasio o los días que faltan para un examen. Java tiene para ello el paquete java.time, moderno, claro y difícil de usar mal. En esta lección aprenderás a crear fechas y horas, a sumarles días o meses, a compararlas, a calcular la diferencia entre dos fechas y a mostrarlas en español con el formato que quieras.

Las clases principales

java.time separa cada concepto en una clase distinta:

ClaseQué representaEjemplo
LocalDateUna fecha sin hora2025-06-18
LocalTimeUna hora sin fecha09:30
LocalDateTimeFecha y hora juntas2025-03-14T17:00
ZonedDateTimeFecha y hora con zona horaria2025-07-01T18:00+02:00[Europe/Madrid]
PeriodUna cantidad de años, meses y días1 año, 9 meses y 5 días
DurationUna cantidad de horas, minutos y segundos6 horas y 35 minutos

“Local” significa que no lleva zona horaria: es la fecha tal como la verías en un calendario de pared. Para la mayoría de programas (reservas, notas, cumpleaños) es justo lo que necesitas.

Todas están en java.time, así que hay que importarlas:

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;

Crear fechas y horas

Hay tres formas: la fecha actual, a partir de números o a partir de un texto.

now(): el momento actual

LocalDate hoy = LocalDate.now();
LocalTime ahora = LocalTime.now();
LocalDateTime momento = LocalDateTime.now();
System.out.println(hoy); // la fecha de hoy, con el formato año-mes-día

El resultado depende del día en que ejecutes el programa, así que en el resto de ejemplos usaremos fechas fijas.

of(): a partir de números

LocalDate examen = LocalDate.of(2025, 6, 18);
System.out.println(examen);                    // 2025-06-18

LocalDate mismaFecha = LocalDate.of(2025, Month.JUNE, 18);

LocalTime apertura = LocalTime.of(9, 30);
System.out.println(apertura);                  // 09:30

LocalDateTime cita = LocalDateTime.of(2025, 3, 14, 17, 0);
System.out.println(cita);                      // 2025-03-14T17:00

A diferencia de las clases antiguas, los meses van del 1 al 12. Si pides una fecha imposible, obtienes una excepción en lugar de un resultado raro:

LocalDate.of(2025, 2, 30); // DateTimeException: Invalid date 'FEBRUARY 30'

parse(): a partir de un texto

LocalDate navidad = LocalDate.parse("2025-12-25");
LocalDateTime cena = LocalDateTime.parse("2025-12-25T20:30");

Sin más indicaciones, parse espera el formato estándar ISO: año-mes-día y una T entre fecha y hora. Más abajo verás cómo leer otros formatos, como 25/12/2025.

Consultar sus partes

System.out.println(examen.getYear());       // 2025
System.out.println(examen.getMonthValue()); // 6
System.out.println(examen.getMonth());      // JUNE
System.out.println(examen.getDayOfMonth()); // 18
System.out.println(examen.getDayOfWeek());  // WEDNESDAY
System.out.println(examen.getDayOfYear());  // 169
System.out.println(examen.isLeapYear());    // false
System.out.println(examen.lengthOfMonth()); // 30

Sumar y restar tiempo

Los métodos plus... y minus... devuelven una fecha nueva:

LocalDate pedido = LocalDate.of(2025, 1, 28);
System.out.println(pedido.plusDays(5));    // 2025-02-02
System.out.println(pedido.plusWeeks(2));   // 2025-02-11
System.out.println(pedido.plusMonths(1));  // 2025-02-28
System.out.println(pedido.minusYears(1));  // 2024-01-28

LocalTime apertura = LocalTime.of(9, 30);
System.out.println(apertura.plusMinutes(45)); // 10:15
System.out.println(LocalTime.of(23, 30).plusHours(2)); // 01:30

Fíjate en que Java cambia de mes y de año solo, y que ajusta los finales de mes: sumar un mes al 31 de enero da el 28 de febrero (o el 29 en un año bisiesto).

Cuidado: las fechas de java.time son inmutables. pedido.plusDays(10); sin guardar el resultado no hace nada: pedido sigue siendo el 28 de enero. Escribe pedido = pedido.plusDays(10);. Es lo mismo que pasa con los métodos de String.

Para cambiar solo una parte existen los métodos with...: pedido.withDayOfMonth(1) da 2025-01-01, el primer día de ese mes.

Comparar fechas

LocalDate inicio = LocalDate.of(2025, 3, 10);
LocalDate fin = LocalDate.of(2025, 4, 2);

System.out.println(inicio.isBefore(fin)); // true
System.out.println(inicio.isAfter(fin));  // false
System.out.println(inicio.isEqual(fin));  // false

También funcionan equals y compareTo (implementan Comparable), así que puedes ordenar una lista de fechas con Collections.sort. Lo que no debes usar es <, > ni ==: son objetos, no números.

Un ejemplo típico: comprobar si una oferta sigue vigente.

LocalDate caducidad = LocalDate.of(2025, 12, 31);
if (LocalDate.now().isAfter(caducidad)) {
    System.out.println("La oferta ha caducado");
}

Diferencia entre fechas: Period, Duration y ChronoUnit

Period: años, meses y días

Period tiempo = Period.between(LocalDate.of(2023, 9, 15), LocalDate.of(2025, 6, 20));
System.out.println(tiempo); // P1Y9M5D
System.out.println(tiempo.getYears() + " años, " + tiempo.getMonths()
        + " meses y " + tiempo.getDays() + " días");
// 1 años, 9 meses y 5 días

P1Y9M5D es el formato ISO: Period de 1 año (Year), 9 meses y 5 días. Un Period también se puede sumar a una fecha: fecha.plus(Period.ofMonths(3)).

Duration: horas, minutos y segundos

Duration mide tiempo “de reloj” y se usa con horas:

Duration jornada = Duration.between(LocalTime.of(8, 15), LocalTime.of(14, 50));
System.out.println(jornada);                 // PT6H35M
System.out.println(jornada.toMinutes());     // 395
System.out.println(jornada.toHours());       // 6
System.out.println(jornada.toMinutesPart()); // 35

Duration clase = Duration.ofMinutes(55);
System.out.println(LocalTime.of(10, 0).plus(clase)); // 10:55

ChronoUnit: total en una sola unidad

Period descompone la diferencia en años, meses y días. Si lo que quieres es el total en una unidad (“¿cuántos días faltan?”), usa ChronoUnit:

import java.time.temporal.ChronoUnit;

long dias = ChronoUnit.DAYS.between(LocalDate.of(2025, 9, 15), LocalDate.of(2025, 12, 22));
System.out.println(dias); // 98

Si la segunda fecha es anterior a la primera, el resultado es negativo (-98). También existen ChronoUnit.WEEKS, MONTHS, YEARS, HOURS, MINUTES…

Formatear fechas con DateTimeFormatter

toString() siempre da el formato ISO (2025-06-18), que en España no es el habitual. Para mostrar o leer otros formatos se usa DateTimeFormatter con un patrón:

import java.time.format.DateTimeFormatter;

DateTimeFormatter formato = DateTimeFormatter.ofPattern("dd/MM/yyyy");
System.out.println(examen.format(formato)); // 18/06/2025

DateTimeFormatter conHora = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
System.out.println(cita.format(conHora));   // 14/03/2025 17:00

LocalDate leida = LocalDate.parse("05/11/2025", formato); // también sirve para leer
System.out.println(leida);                  // 2025-11-05

Las letras más usadas en los patrones:

LetraSignificadoEjemplo
d / ddDía del mes5 / 05
M / MMMes en número6 / 06
MMM / MMMMMes en textojun / junio
yyyyAño2025
EEE / EEEEDía de la semanamié / miércoles
HHHora de 0 a 2317
hhHora de 1 a 1205
mmMinutos05
ssSegundos09
'texto'Texto literal'de'

Cuidado: MM son meses y mm son minutos. Y HH es la hora de 24 horas, mientras que hh es la de 12. Confundirlas es el error más habitual con los patrones.

En español con Locale

Los nombres de meses y días dependen del idioma. Para que salgan en español, pásale un Locale al formateador:

import java.util.Locale;

Locale es = Locale.of("es", "ES");
DateTimeFormatter largo = DateTimeFormatter.ofPattern("EEEE, d 'de' MMMM 'de' yyyy", es);
System.out.println(examen.format(largo)); // miércoles, 18 de junio de 2025

DateTimeFormatter corto = DateTimeFormatter.ofPattern("EEE d MMM", es);
System.out.println(examen.format(corto)); // mié 18 jun

Locale.of existe desde Java 19; en versiones anteriores se escribía new Locale("es", "ES"). Si no indicas Locale, se usa el del sistema, y el mismo programa mostraría “Wednesday” en un ordenador configurado en inglés.

Fechas incorrectas al leer

Si el texto no encaja con el patrón, parse lanza DateTimeParseException, que conviene capturar cuando el dato viene del usuario:

try {
    LocalDate.parse("18/6/2025", formato);  // falta el cero: el patrón pide dd/MM
} catch (DateTimeParseException e) {
    System.out.println("Fecha no válida: " + e.getMessage());
}
// Fecha no válida: Text '18/6/2025' could not be parsed at index 3

Un detalle: con el patrón dd/MM/yyyy, el texto 31/02/2025 no da error, sino que se ajusta a 2025-02-28. Si quieres rechazar fechas imposibles, usa el modo estricto (con uuuu para el año):

DateTimeFormatter estricto = DateTimeFormatter.ofPattern("dd/MM/uuuu")
        .withResolverStyle(ResolverStyle.STRICT);
LocalDate.parse("31/02/2025", estricto); // DateTimeParseException: Invalid date 'FEBRUARY 31'

Días de la semana: DayOfWeek

getDayOfWeek() devuelve un enum DayOfWeek con valores de MONDAY (1) a SUNDAY (7):

DayOfWeek dia = examen.getDayOfWeek();
System.out.println(dia + " " + dia.getValue());           // WEDNESDAY 3
System.out.println(dia.getDisplayName(TextStyle.FULL, es)); // miércoles

boolean esFinDeSemana = dia == DayOfWeek.SATURDAY || dia == DayOfWeek.SUNDAY;
System.out.println(esFinDeSemana); // false

Al ser un enum, se puede usar en un switch. Month funciona igual: Month.MARCH.getDisplayName(TextStyle.FULL, es) da marzo.

Ejemplo: calcular la edad

Calcular la edad a mano (restar años y comprobar si ya ha sido el cumpleaños) es fácil de hacer mal. Con Period es una línea:

public static int calcularEdad(LocalDate nacimiento, LocalDate hoy) {
    return Period.between(nacimiento, hoy).getYears();
}

LocalDate nacimiento = LocalDate.of(2005, 11, 3);
System.out.println(calcularEdad(nacimiento, LocalDate.of(2025, 11, 2))); // 19
System.out.println(calcularEdad(nacimiento, LocalDate.of(2025, 11, 3))); // 20

En tu programa llamarías a calcularEdad(nacimiento, LocalDate.now()). Pasar la fecha de hoy como parámetro hace que el método sea fácil de probar con fechas fijas.

Zonas horarias: ZonedDateTime

Si tu aplicación coordina personas de distintos países (una videollamada entre Madrid y México), necesitas zonas horarias. Se identifican con nombres como Europe/Madrid:

ZonedDateTime reunion = ZonedDateTime.of(
        LocalDateTime.of(2025, 7, 1, 18, 0), ZoneId.of("Europe/Madrid"));
System.out.println(reunion);
// 2025-07-01T18:00+02:00[Europe/Madrid]

System.out.println(reunion.withZoneSameInstant(ZoneId.of("America/Mexico_City")));
// 2025-07-01T10:00-06:00[America/Mexico_City]

System.out.println(reunion.withZoneSameInstant(ZoneId.of("Atlantic/Canary")));
// 2025-07-01T17:00+01:00[Atlantic/Canary]

ZonedDateTime tiene en cuenta los cambios de horario de verano: en julio Madrid está en +02:00 y en enero en +01:00. Usa siempre el nombre de la zona, no un desfase fijo.

Por qué no Date ni Calendar

En código antiguo y en muchos apuntes verás java.util.Date y java.util.Calendar. Evítalas en código nuevo:

  • Son mutables: cualquier parte del programa puede cambiar una fecha que compartes.
  • Los meses empiezan en 0: enero es 0 y diciembre 11, fuente de errores constantes.
  • Date mezcla fecha, hora y zona en un solo objeto confuso, y muchos de sus métodos están obsoletos.
  • SimpleDateFormat no es seguro si varios hilos lo usan a la vez.

java.time (desde Java 8) corrige todo esto. Si una biblioteca antigua te da un Date, conviértelo: date.toInstant() te da un Instant de java.time.

Errores frecuentes

  • No guardar el resultado de plusDays: las fechas son inmutables.
  • Confundir MM (mes) con mm (minutos) o HH con hh en los patrones.
  • Comparar fechas con <, > o ==. Usa isBefore, isAfter e isEqual.
  • Usar Period.getDays() para saber los días totales. Solo da la parte de días; para el total usa ChronoUnit.DAYS.between.
  • Olvidar el Locale y obtener los meses en inglés.
  • No capturar DateTimeParseException al leer fechas que escribe el usuario.
  • Mostrar en una prueba la salida de now() como si fuera fija: cambia cada día.

Resumen

TareaCódigo
Fecha / hora actualLocalDate.now(), LocalTime.now(), LocalDateTime.now()
CrearLocalDate.of(2025, 6, 18), LocalTime.of(9, 30)
Desde textoLocalDate.parse("2025-06-18"), LocalDate.parse(t, formato)
Sumar / restarplusDays(5), minusMonths(1), plusHours(2)
CompararisBefore, isAfter, isEqual
DiferenciaPeriod.between, Duration.between, ChronoUnit.DAYS.between
Formatearfecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
En españolofPattern("EEEE d 'de' MMMM", Locale.of("es", "ES"))
Día de la semanafecha.getDayOfWeek() → DayOfWeek
Zona horariaZonedDateTime.of(..., ZoneId.of("Europe/Madrid"))

En la siguiente lección aprenderás a guardar los datos de tus programas para que no se pierdan al cerrarlos: leer y escribir ficheros.

Pon a prueba lo que has aprendido

[Java] ¿Qué imprime este código?
LocalDate entrega = LocalDate.of(2025, 5, 30);
entrega.plusDays(3);
System.out.println(entrega);

[Java] ¿Qué imprime este código?
LocalDate f = LocalDate.of(2025, 12, 30).plusDays(5);
System.out.println(f.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")));

[Java] ¿Qué imprime este código?
LocalDate a = LocalDate.of(2025, 1, 10);
LocalDate b = LocalDate.of(2025, 3, 15);
Period p = Period.between(a, b);
long d = ChronoUnit.DAYS.between(a, b);
System.out.println(p.getDays() + " " + d);

[Java] ¿Por qué falla LocalTime.of(9, 5).format(DateTimeFormatter.ofPattern("HH:MM"))?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.