Fechas y horas en Java con java.time
En esta lección
- Las clases principales
- Crear fechas y horas
- Sumar y restar tiempo
- Comparar fechas
- Diferencia entre fechas: Period, Duration y ChronoUnit
- Formatear fechas con DateTimeFormatter
- Días de la semana: DayOfWeek
- Ejemplo: calcular la edad
- Zonas horarias: ZonedDateTime
- Por qué no Date ni Calendar
- Errores frecuentes
- 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:
| Clase | Qué representa | Ejemplo |
|---|---|---|
LocalDate | Una fecha sin hora | 2025-06-18 |
LocalTime | Una hora sin fecha | 09:30 |
LocalDateTime | Fecha y hora juntas | 2025-03-14T17:00 |
ZonedDateTime | Fecha y hora con zona horaria | 2025-07-01T18:00+02:00[Europe/Madrid] |
Period | Una cantidad de años, meses y días | 1 año, 9 meses y 5 días |
Duration | Una cantidad de horas, minutos y segundos | 6 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.timeson inmutables.pedido.plusDays(10);sin guardar el resultado no hace nada:pedidosigue siendo el 28 de enero. Escribepedido = 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:
| Letra | Significado | Ejemplo |
|---|---|---|
d / dd | Día del mes | 5 / 05 |
M / MM | Mes en número | 6 / 06 |
MMM / MMMM | Mes en texto | jun / junio |
yyyy | Año | 2025 |
EEE / EEEE | Día de la semana | mié / miércoles |
HH | Hora de 0 a 23 | 17 |
hh | Hora de 1 a 12 | 05 |
mm | Minutos | 05 |
ss | Segundos | 09 |
'texto' | Texto literal | 'de' |
Cuidado:
MMson meses ymmson minutos. YHHes la hora de 24 horas, mientras quehhes 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.
Datemezcla fecha, hora y zona en un solo objeto confuso, y muchos de sus métodos están obsoletos.SimpleDateFormatno 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) conmm(minutos) oHHconhhen los patrones. - Comparar fechas con
<,>o==. UsaisBefore,isAftereisEqual. - Usar
Period.getDays()para saber los días totales. Solo da la parte de días; para el total usaChronoUnit.DAYS.between. - Olvidar el
Localey obtener los meses en inglés. - No capturar
DateTimeParseExceptional leer fechas que escribe el usuario. - Mostrar en una prueba la salida de
now()como si fuera fija: cambia cada día.
Resumen
| Tarea | Código |
|---|---|
| Fecha / hora actual | LocalDate.now(), LocalTime.now(), LocalDateTime.now() |
| Crear | LocalDate.of(2025, 6, 18), LocalTime.of(9, 30) |
| Desde texto | LocalDate.parse("2025-06-18"), LocalDate.parse(t, formato) |
| Sumar / restar | plusDays(5), minusMonths(1), plusHours(2) |
| Comparar | isBefore, isAfter, isEqual |
| Diferencia | Period.between, Duration.between, ChronoUnit.DAYS.between |
| Formatear | fecha.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")) |
| En español | ofPattern("EEEE d 'de' MMMM", Locale.of("es", "ES")) |
| Día de la semana | fecha.getDayOfWeek() → DayOfWeek |
| Zona horaria | ZonedDateTime.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
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.