Saltar a contenido

UD2 · Sección 6 — Fechas y tiempos: java.time

Dato Valor
Unidad Didáctica UD2 — Programación Orientada a Objetos con la API de Java
Resultado de Aprendizaje RA2 — Escribe y prueba programas sencillos, reconociendo y aplicando los fundamentos de la programación orientada a objetos.
Sección 6 de 10 — Fechas y tiempos: java.time
Evaluación 1.ª Evaluación
Duración estimada 3 horas lectivas
Criterios de evaluación cubiertos CE 2.2 (escribe programas simples), CE 2.3 (instancia objetos a partir de clases predefinidas), CE 2.4 (utiliza métodos y propiedades), CE 2.5 (llamadas a métodos estáticos), CE 2.6 (utiliza parámetros), CE 2.7 (incorpora y utiliza librerías de objetos), CE 2.8 (utiliza constructores)
Prerrequisitos UD1 completa, UD2 Secciones 1 a 5
Paquete del código de ejemplo dam.programacion.ud2
Herramientas JDK 25, IntelliJ IDEA Community, Maven 3.9.x

1. Punto de partida

Trabajar con fechas y horas parece trivial hasta que te enfrentas a ello. ¿Cuántos días hay entre el 1 de enero y el 15 de marzo? ¿Y si hay un año bisiesto por medio? ¿En qué cambia la hora de Madrid respecto a Tokio? ¿Y respecto a la hora UTC? ¿Cuántos segundos tiene un mes de febrero? Si has intentado responder alguna de estas preguntas a mano, sabes que la cosa se complica enseguida.

Históricamente, Java ofrecía las clases Date y Calendar (en java.util), que eran notoriamente difíciles de usar: meses numerados desde 0, mutabilidad no esperada, zonas horarias mal gestionadas, API confusa. En Java 8 (2014) se introdujo el paquete java.time, basado en la librería Joda-Time, que resolvió todos esos problemas. Desde entonces, Date y Calendar se consideran obsoletos para nuevo código: los seguirás viendo en proyectos viejos, pero tú no deberías usarlos.

En esta sección vas a conocer las cinco clases principales de java.time para representar momentos (LocalDate, LocalTime, LocalDateTime, ZonedDateTime), los dos tipos para intervalos (Period y Duration), y la clase para formatear y parsear (DateTimeFormatter). Al terminar, serás capaz de representar cualquier fecha u hora, calcular diferencias y formatearlas para mostrarlas al usuario.


2. Por qué java.time reemplaza a Date y Calendar

Antes de entrar en la nueva API, conviene entender por qué se considera que Date y Calendar están "deprecados" para nuevo código. Esto te ayudará a apreciar las decisiones de diseño de java.time y a no caer en los mismos errores si los ves en código heredado.

Problema con Date / Calendar Cómo lo resuelve java.time
Date es mutable: un método puede modificar tu fecha sin que te enteres Todas las clases de java.time son inmutables
Calendar numera los meses desde 0 (enero = 0, diciembre = 11) java.time usa Month.JANUARY hasta Month.DECEMBER (1 a 12)
Date representa un instante concreto, no "una fecha" abstracta LocalDate representa solo una fecha, sin hora ni zona
Los años en Date se cuentan desde 1900 (getYear() devuelve año - 1900) LocalDate.getYear() devuelve el año completo (2026)
La API mezcla conceptos (fecha, hora, instante, zona) en una sola clase java.time separa conceptos en clases distintas
SimpleDateFormat no es seguro en concurrencia DateTimeFormatter es inmutable y seguro

Regla práctica: en código nuevo, usa siempre java.time. Si te encuentras Date o Calendar en un proyecto viejo, conviértelos a java.time tan pronto como puedas (tienes métodos como Date.toInstant() y Date.from(Instant) para hacer el puente).


3. Las cinco clases principales

java.time separa los conceptos en clases distintas, cada una con una misión clara. Esa separación es la clave de su claridad.

Use mouse to pan and zoom
graph TD
    A["java.time"]
    A --> LD["LocalDate<br/>fecha sin hora"]
    A --> LT["LocalTime<br/>hora sin fecha"]
    A --> LDT["LocalDateTime<br/>fecha y hora"]
    A --> ZDT["ZonedDateTime<br/>fecha, hora y zona"]
    A --> I["Instant<br/>instante en UTC"]

    LD -->|"combina con"| LT
    LD --> LDT
    LT --> LDT
    LDT -->|"añade zona"| ZDT
    I -.se asocia a.-> ZDT

    style A fill:#1565c0,color:#fff,stroke:#0d47a1
    style LD fill:#2e7d32,color:#fff,stroke:#1b5e20
    style LT fill:#2e7d32,color:#fff,stroke:#1b5e20
    style LDT fill:#ef6c00,color:#fff,stroke:#e65100
    style ZDT fill:#6a1b9a,color:#fff,stroke:#4a148c
    style I fill:#c62828,color:#fff,stroke:#b71c1c
Clase Qué representa Ejemplo
LocalDate Una fecha sin hora ni zona (14/07/2026) Tu cumpleaños
LocalTime Una hora sin fecha ni zona (17:30) La hora a la que suena tu alarma
LocalDateTime Fecha + hora, sin zona (14/07/2026 17:30) La hora de inicio de un evento local
ZonedDateTime Fecha + hora + zona horaria (14/07/2026 17:30 Europe/Madrid) Una reunión internacional
Instant Un instante preciso en UTC (linea temporal universal) Marca de tiempo de un log

Analogía de la cita. Imagina que quedas con un amigo para tomar un café:

  • LocalDate: "Quedamos el 14 de julio" (sin hora, sin ciudad).
  • LocalTime: "Quedamos a las 17:30" (sin fecha, sin ciudad).
  • LocalDateTime: "Quedamos el 14 de julio a las 17:30" (sin ciudad).
  • ZonedDateTime: "Quedamos el 14 de julio a las 17:30 en Madrid" (con todo). Si tu amigo vive en México, sabrá exactamente a qué hora local le corresponde.
  • Instant: "Quedamos en el segundo 1.783.642.000 del reloj UTC" (lo que usan los ordenadores entre ellos).

Para la mayoría de aplicaciones de escritorio o de un solo país, LocalDate y LocalDateTime son suficientes. Para aplicaciones internacionales o servidores, conviene usar ZonedDateTime o Instant.

En esta sección nos centraremos en las tres primeras (LocalDate, LocalTime, LocalDateTime) y presentaremos ZonedDateTime e Instant brevemente.


4. LocalDate: fecha sin hora

LocalDate representa una fecha: año, mes y día, sin hora y sin zona horaria. Es la clase que más usarás para representar cumpleaños, fechas de vencimiento, días festivos, etc.

4.1. Crear un LocalDate

Hay tres formas principales:

import java.time.LocalDate;

// 1. Fecha actual (relój del sistema)
LocalDate hoy = LocalDate.now();

// 2. Fecha concreta con of
LocalDate cumple = LocalDate.of(2005, 7, 14);   // 14 de julio de 2005
LocalDate cumple2 = LocalDate.of(2005, Month.JULY, 14);  // mas legible

// 3. Parsear desde un texto (formato ISO por defecto: yyyy-MM-dd)
LocalDate navidad = LocalDate.parse("2026-12-25");

Trampa de los meses. A diferencia del viejo Calendar (que numeraba enero como 0), LocalDate.of(2026, 7, 14) significa 14 de julio. Los meses van de 1 a 12. Para mayor claridad, usa la enumeración Month.JANUARY...Month.DECEMBER.

4.2. Métodos principales

Método Qué hace Devuelve
now() Fecha actual (método estático) LocalDate
of(int año, int mes, int dia) Crea una fecha concreta (estático) LocalDate
parse(String) Parsea en formato ISO (estático) LocalDate
getYear() Año int
getMonthValue() Mes (1-12) int
getMonth() Mes como enumeración Month
getDayOfMonth() Día del mes int
getDayOfWeek() Día de la semana DayOfWeek
getDayOfYear() Día del año (1-366) int
isLeapYear() ¿Es año bisiesto? boolean
lengthOfMonth() Días del mes int
lengthOfYear() Días del año int
plusDays(long) Suma días LocalDate
plusMonths(long) Suma meses LocalDate
plusYears(long) Suma años LocalDate
plusWeeks(long) Suma semanas LocalDate
minusDays(long) Resta días LocalDate
minusMonths(long) Resta meses LocalDate
minusYears(long) Resta años LocalDate
isBefore(LocalDate) ¿Es anterior a otra? boolean
isAfter(LocalDate) ¿Es posterior a otra? boolean
isEqual(LocalDate) ¿Es igual a otra? boolean

Inmutabilidad. Como en String, los métodos plus* y minus* no modifican el objeto original: devuelven un LocalDate nuevo. Si olvidas asignar el resultado, no pasa nada.

1
2
3
4
LocalDate hoy = LocalDate.now();   // p.ej. 2026-07-14
LocalDate manana = hoy.plusDays(1);  // 2026-07-15
System.out.println(hoy);    // sigue siendo 2026-07-14
System.out.println(manana); // 2026-07-15

4.3. Ejemplo práctico

import java.time.LocalDate;
import java.time.Month;
import java.time.DayOfWeek;

public class Fechas {
    public static void main(String[] args) {
        LocalDate hoy = LocalDate.now();
        LocalDate cumple = LocalDate.of(2005, Month.JULY, 14);

        System.out.println("Hoy: " + hoy);
        System.out.println("Anio: " + hoy.getYear());
        System.out.println("Mes: " + hoy.getMonthValue() + " (" + hoy.getMonth() + ")");
        System.out.println("Dia del mes: " + hoy.getDayOfMonth());
        System.out.println("Dia de la semana: " + hoy.getDayOfWeek());
        System.out.println("Dia del anio: " + hoy.getDayOfYear());
        System.out.println("Es bisiesto: " + hoy.isLeapYear());
        System.out.println("Dias del mes: " + hoy.lengthOfMonth());

        // Proximo cumple
        LocalDate proximoCumple = cumple.withYear(hoy.getYear());
        if (proximoCumple.isBefore(hoy)) {
            proximoCumple = proximoCumple.plusYears(1);
        }
        System.out.println("Proximo cumple: " + proximoCumple);
        System.out.println("Faltan " + (proximoCumple.getDayOfYear() - hoy.getDayOfYear()) + " dias (aprox)");
    }
}

Nota sobre withYear. Es un método que devuelve un LocalDate con el año cambiado pero conservando mes y día. Lo verás más en el ejemplo integrador; la idea general es que los métodos with* permiten "ajustar" un campo concreto.


5. LocalTime: hora sin fecha

LocalTime representa una hora del día: horas, minutos, segundos y nanosegundos, sin fecha ni zona. Útil para representar horas de apertura, alarmas, duraciones puntuales.

5.1. Crear un LocalTime

1
2
3
4
5
6
7
import java.time.LocalTime;

LocalTime ahora = LocalTime.now();              // hora actual
LocalTime mediodia = LocalTime.of(12, 0);       // 12:00
LocalTime horaExacta = LocalTime.of(17, 30, 45); // 17:30:45
LocalTime conNanos = LocalTime.of(17, 30, 45, 123456789); // con nanosegundos
LocalTime parseada = LocalTime.parse("17:30");   // 17:30

5.2. Métodos principales

Método Qué hace
now() Hora actual (estático)
of(int h, int m) / of(int h, int m, int s) / of(int h, int m, int s, int ns) Crea hora concreta (estático)
parse(String) Parsea en formato ISO "HH:mm" o "HH:mm:ss" (estático)
getHour(), getMinute(), getSecond(), getNano() Componentes
plusHours(long), plusMinutes(long), plusSeconds(long) Sumar
minusHours(long), minusMinutes(long), minusSeconds(long) Restar
isBefore(LocalTime), isAfter(LocalTime) Comparación

5.3. Ejemplo

import java.time.LocalTime;

public class Horas {
    public static void main(String[] args) {
        LocalTime ahora = LocalTime.now();
        LocalTime apertura = LocalTime.of(9, 0);
        LocalTime cierre = LocalTime.of(21, 30);

        System.out.println("Ahora: " + ahora);
        System.out.println("Apertura: " + apertura);
        System.out.println("Cierre: " + cierre);

        boolean abierto = !ahora.isBefore(apertura) && ahora.isBefore(cierre);
        System.out.println("Esta abierto: " + abierto);

        // Cuanto falta para cerrar
        long minutosFaltan = java.time.Duration.between(ahora, cierre).toMinutes();
        System.out.println("Minutos para cerrar: " + minutosFaltan);
    }
}

Duration lo veremos en la sección 8; el ejemplo te adelanta su uso más común.


6. LocalDateTime: fecha + hora

LocalDateTime combina LocalDate y LocalTime: tiene año, mes, día, hora, minuto, segundo y nanosegundo, pero sin zona horaria. Es lo que usarás cuando necesites representar "el momento exacto de un evento local" sin preocuparte de zonas horarias.

6.1. Crear un LocalDateTime

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

// 1. Fecha y hora actual
LocalDateTime ahora = LocalDateTime.now();

// 2. Fecha y hora concretas
LocalDateTime evento = LocalDateTime.of(2026, 7, 14, 17, 30);
LocalDateTime evento2 = LocalDateTime.of(2026, 7, 14, 17, 30, 45);

// 3. Combinando LocalDate y LocalTime
LocalDate fecha = LocalDate.of(2026, 7, 14);
LocalTime hora = LocalTime.of(17, 30);
LocalDateTime combinado = LocalDateTime.of(fecha, hora);

// 4. A partir de un LocalDate, añadiendo hora
LocalDateTime conHora = fecha.atTime(17, 30);

// 5. A partir de un LocalTime, añadiendo fecha
LocalDateTime conFecha = hora.atDate(fecha);

Los métodos atTime y atDate son muy expresivos: "esta fecha, a esta hora" o "esta hora, en esta fecha".

6.2. Métodos principales

Los mismos de LocalDate y LocalTime combinados: getYear, getMonthValue, getDayOfMonth, getHour, getMinute, getSecond, plusDays, plusHours, minusMonths, etc.

1
2
3
4
LocalDateTime ahora = LocalDateTime.now();
LocalDateTime enUnaSemana = ahora.plusWeeks(1);
LocalDateTime haceUnMes = ahora.minusMonths(1);
LocalDateTime mismoInstanteOtroDia = ahora.withDayOfMonth(1);

7. ZonedDateTime e Instant (introducción)

7.1. ZonedDateTime

Añade la zona horaria a un LocalDateTime. Necesario cuando trabajas con eventos internacionales o quieres evitar ambigüedades con cambios de hora (verano/invierno).

import java.time.ZonedDateTime;
import java.time.ZoneId;

ZonedDateTime ahoraMadrid = ZonedDateTime.now(ZoneId.of("Europe/Madrid"));
ZonedDateTime ahoraTokio = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));

System.out.println("Madrid: " + ahoraMadrid);
System.out.println("Tokio: " + ahoraTokio);
// Madrid: 2026-07-14T17:30:45.123+02:00[Europe/Madrid]
// Tokio:  2026-07-15T00:30:45.123+09:00[Asia/Tokyo]

Como ves, aunque la hora local en Madrid y Tokio es distinta, representan el mismo instante universal: cuando en Madrid son las 17:30 del 14 de julio, en Tokio son las 00:30 del 15 de julio.

7.2. Instant

Instant representa un instante preciso en la línea temporal UTC, con precisión de nanosegundo. Es lo que usan los sistemas internamente para marcar tiempos (logs, base de datos, eventos).

import java.time.Instant;

Instant ahora = Instant.now();
System.out.println(ahora);   // 2026-07-14T15:30:45.123Z (la Z indica UTC)

// Segundos desde el epoch Unix (1 de enero de 1970, 00:00 UTC)
System.out.println(ahora.getEpochSecond());   // p.ej. 1783642000

// Convertir Instant a ZonedDateTime
ZonedDateTime enMadrid = ahora.atZone(ZoneId.of("Europe/Madrid"));

Diferencia clave: Instant no tiene zona horaria (es UTC implícito), ZonedDateTime sí la tiene. Instant es para máquinas; ZonedDateTime es para humanos.

7.3. Cuándo usar cada uno

Necesidad Clase
Cumpleaños, festivos, vencimientos LocalDate
Horas de apertura, alarmas LocalTime
Eventos locales sin zona LocalDateTime
Reuniones internacionales, reservas de vuelo ZonedDateTime
Marcas de tiempo en logs, base de datos Instant

8. Period y Duration: intervalos de tiempo

java.time distingue dos tipos de intervalos:

  • Period: intervalo en términos de años, meses y días. Apropiado para diferencias entre fechas (humanas).
  • Duration: intervalo en términos de horas, minutos, segundos y nanosegundos. Apropiado para diferencias entre instantes (mecánicas).

8.1. Period

import java.time.LocalDate;
import java.time.Period;

LocalDate nacimiento = LocalDate.of(2005, 7, 14);
LocalDate hoy = LocalDate.now();

Period edad = Period.between(nacimiento, hoy);
System.out.println(edad.getYears() + " anios, " + edad.getMonths() + " meses, " + edad.getDays() + " dias");
// p.ej. 21 anios, 0 meses, 0 dias

// Sumar un periodo a una fecha
LocalDate enUnAnioYMedio = hoy.plus(Period.ofYears(1).plusMonths(6));
System.out.println(enUnAnioYMedio);

8.2. Duration

import java.time.LocalTime;
import java.time.Duration;

LocalTime inicio = LocalTime.of(9, 0);
LocalTime fin = LocalTime.of(17, 30);

Duration turno = Duration.between(inicio, fin);
System.out.println(turno.toHours() + " horas");     // 8 horas
System.out.println(turno.toMinutes() + " minutos"); // 510 minutos
System.out.println(turno.toSeconds() + " segundos"); // 30600 segundos

8.3. Diferencia entre Period y Duration

Aspecto Period Duration
Unidades Años, meses, días Horas, minutos, segundos, nanos
Basado en Campos calendáricos (humanos) Tiempo absoluto (segundos)
Apropiado para "Tengo 21 años" "El turno dura 8 horas"
between(a, b) Entre dos LocalDate (o LocalDateTime) Entre dos LocalTime (o Instant)
Sumar a LocalDate Sí, con plus(Period) No
Sumar a LocalTime No Sí, con plus(Duration)

Trampa. Duration entre dos LocalDateTime calcula la diferencia absoluta en segundos, sin tener en cuenta cambios de horario de verano/invierno. Period entre dos LocalDate sí respeta el calendario (un mes es un mes, tenga 28 o 31 días). Por eso:

  • Para "cuántos años/cumpleaños", usa Period.
  • Para "cuántas horas falta", usa Duration.

9. DateTimeFormatter: formatear y parsear

Hasta ahora, al imprimir las fechas hemos visto el formato ISO (2026-07-14T17:30:45). Pero los humanos solemos preferir formatos más naturales como "14/07/2026" o "14 de julio de 2026". Para personalizar el formato, se usa DateTimeFormatter.

9.1. Formateadores predefinidos

java.time.format.DateTimeFormatter ofrece algunos formateadores ya listos:

Formateador Formato Ejemplo
ISO_LOCAL_DATE yyyy-MM-dd 2026-07-14
ISO_LOCAL_TIME HH:mm:ss 17:30:45
ISO_LOCAL_DATE_TIME yyyy-MM-dd'T'HH:mm:ss 2026-07-14T17:30:45
ofLocalizedDate(...) Formato localizado de fecha 14 jul 2026
ofLocalizedTime(...) Formato localizado de hora 17:30
ofLocalizedDateTime(...) Formato localizado de fecha+hora 14-jul-2026 17:30

9.2. Formateadores personalizados

Para crear tu propio formato, usa patrones con letras especiales:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

LocalDate fecha = LocalDate.of(2026, 7, 14);

DateTimeFormatter f1 = DateTimeFormatter.ofPattern("dd/MM/yyyy");
DateTimeFormatter f2 = DateTimeFormatter.ofPattern("d 'de' MMMM 'de' yyyy");
DateTimeFormatter f3 = DateTimeFormatter.ofPattern("EEEE, d MMM yyyy");

System.out.println(fecha.format(f1));   // 14/07/2026
System.out.println(fecha.format(f2));   // 14 de julio de 2026
System.out.println(fecha.format(f3));   // martes, 14 jul 2026

9.3. Patrones más comunes

Letra Significado Ejemplo
yyyy Año (4 dígitos) 2026
yy Año (2 dígitos) 26
MM Mes (2 dígitos, 01-12) 07
MMM Mes abreviado jul
MMMM Mes completo julio
dd Día del mes (2 dígitos) 14
d Día del mes (sin ceros) 14
EEEE Día de la semana completo martes
EEE Día de la semana abreviado mar
HH Hora (24h, 00-23) 17
hh Hora (12h, 01-12) 05
mm Minutos 30
ss Segundos 45
a AM/PM PM

9.4. Parsear con un formato personalizado

1
2
3
4
5
6
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate fecha = LocalDate.parse("14/07/2026", f);
System.out.println(fecha);   // 2026-07-14 (formato ISO al imprimir por defecto)

Si el texto no encaja con el patrón, se lanza DateTimeParseException. Más adelante, en UD3, verás cómo gestionar estas excepciones con try-catch.

9.5. Localización

Los nombres de meses y días salen en el idioma por defecto de la JVM. Para forzar un idioma concreto:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

LocalDate fecha = LocalDate.of(2026, 7, 14);
DateTimeFormatter f = DateTimeFormatter
    .ofPattern("d 'de' MMMM 'de' yyyy", new Locale("es", "ES"));
System.out.println(fecha.format(f));   // 14 de julio de 2026

DateTimeFormatter fEn = DateTimeFormatter
    .ofPattern("d MMMM yyyy", Locale.ENGLISH);
System.out.println(fecha.format(fEn)); // 14 July 2026

10. Ejemplo integrador

Programa que combina los conceptos de la sección. Simula una pequeña gestión de eventos: crea un evento, calcula días hasta el evento, calcula edad, gestiona horarios y formatea todo de forma amigable.

package dam.programacion.ud2;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.Period;
import java.time.Duration;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Locale;

/**
 * Ejemplo integrador de la Seccion 6 de UD2.
 * Demuestra:
 * - LocalDate, LocalTime, LocalDateTime
 * - Period y Duration
 * - DateTimeFormatter
 * - Inmutabilidad de las clases de java.time
 */
public class GestionEventos {

    public static void main(String[] args) {

        // 1. Fecha y hora actual
        LocalDate hoy = LocalDate.now();
        LocalTime ahora = LocalTime.now();
        LocalDateTime ahoraCompleto = LocalDateTime.now();
        System.out.println("Hoy: " + hoy);
        System.out.println("Ahora: " + ahora);
        System.out.println("Completo: " + ahoraCompleto);
        System.out.println();

        // 2. Crear un evento futuro
        LocalDate fechaEvento = LocalDate.of(2026, Month.OCTOBER, 15);
        LocalTime horaEvento = LocalTime.of(18, 30);
        LocalDateTime evento = LocalDateTime.of(fechaEvento, horaEvento);
        System.out.println("Evento: " + evento);

        // 3. Cuantos dias faltan (Period)
        Period falta = Period.between(hoy, fechaEvento);
        System.out.println("Faltan " + falta.getYears() + " anios, "
                + falta.getMonths() + " meses y " + falta.getDays() + " dias");

        // 4. Cuantas horas faltan (Duration)
        Duration horasFaltan = Duration.between(ahoraCompleto, evento);
        System.out.println("Equivale a " + horasFaltan.toHours() + " horas");
        System.out.println();

        // 5. Edad de un usuario
        LocalDate nacimiento = LocalDate.of(2005, Month.JULY, 14);
        Period edad = Period.between(nacimiento, hoy);
        System.out.println("Edad: " + edad.getYears() + " anios");
        System.out.println();

        // 6. Formatear fechas en espanol
        DateTimeFormatter fCorto = DateTimeFormatter.ofPattern("dd/MM/yyyy");
        DateTimeFormatter fLargo = DateTimeFormatter
            .ofPattern("d 'de' MMMM 'de' yyyy", new Locale("es", "ES"));
        DateTimeFormatter fCompleto = DateTimeFormatter
            .ofPattern("EEEE d 'de' MMMM 'de' yyyy 'a las' HH:mm",
                new Locale("es", "ES"));

        System.out.println("Fecha corta:  " + fechaEvento.format(fCorto));
        System.out.println("Fecha larga:  " + fechaEvento.format(fLargo));
        System.out.println("Evento:       " + evento.format(fCompleto));
        System.out.println();

        // 7. Parsear una fecha introducida por el usuario
        String entrada = "25/12/2026";
        try {
            LocalDate navidad = LocalDate.parse(entrada, fCorto);
            System.out.println("Parseado: " + navidad);
            System.out.println("Es bisiesto: " + navidad.isLeapYear());
            System.out.println("Dias en diciembre: " + navidad.lengthOfMonth());
        } catch (DateTimeParseException e) {
            System.out.println("Formato invalido: " + e.getMessage());
        }
        System.out.println();

        // 8. Demostrar inmutabilidad
        LocalDate prueba = LocalDate.of(2026, 1, 1);
        prueba.plusMonths(1);   // el resultado se descarta
        System.out.println("Sin asignar: " + prueba);  // sigue 2026-01-01

        prueba = prueba.plusMonths(1);   // ahora si
        System.out.println("Asignado:    " + prueba);  // 2026-02-01
    }
}

Salida (parcial; la fecha actual y los cálculos relativos dependen del momento de ejecución):

Hoy: 2026-07-14
Ahora: 17:30:45.123
Completo: 2026-07-14T17:30:45.123

Evento: 2026-10-15T18:30
Faltan 0 anios, 3 meses y 1 dias
Equivale a 2241 horas

Edad: 20 anios

Fecha corta:  15/10/2026
Fecha larga:  15 de octubre de 2026
Evento:       jueves 15 de octubre de 2026 a las 18:30

Parseado: 2026-12-25
Es bisiesto: false
Dias en diciembre: 31

Sin asignar: 2026-01-01
Asignado:    2026-02-01

11. Errores comunes

Error Qué pasa Cómo evitarlo
Olvidar el import "cannot find symbol" Añade los imports correspondientes (java.time.LocalDate, etc.)
Esperar que plus* modifique el objeto original El cambio se pierde Asigna el resultado: fecha = fecha.plusDays(1);
Confundir Period y Duration Resultados inesperados al calcular intervalos Period para años/meses/días entre fechas; Duration para horas/minutos/segundos entre instantes
Usar Date o Calendar en código nuevo API confusa y propensa a errores Usa siempre java.time
Olvidar el Locale al formatear en español Los nombres salen en el idioma por defecto de la JVM Pasa new Locale("es", "ES") al DateTimeFormatter
Confundir MMM con MM MM es numérico, MMM es texto Revisa la tabla de patrones
Pasar fechas en formato no ISO a parse(String) sin DateTimeFormatter DateTimeParseException Usa parse(String, DateTimeFormatter) para formatos personalizados
Usar LocalDateTime para eventos internacionales Errores con cambios horarios (verano/invierno) Usa ZonedDateTime o Instant
Pensar que los meses van de 0 a 11 Esa trampa es del viejo Calendar En java.time, los meses van de 1 a 12
Olvidar el manejo de DateTimeParseException al parsear entrada de usuario El programa falla si el formato es incorrecto Usa try-catch (lo verás en UD3)

12. Resumen

Concepto Idea clave
LocalDate Fecha sin hora ni zona (14/07/2026)
LocalTime Hora sin fecha ni zona (17:30)
LocalDateTime Fecha + hora sin zona
ZonedDateTime Fecha + hora + zona horaria
Instant Instante preciso en UTC (para máquinas)
Period Intervalo en años/meses/días
Duration Intervalo en horas/minutos/segundos
DateTimeFormatter Formatea y parsea fechas con patrones
Inmutabilidad Todos los métodos devuelven nuevas instancias
now() Método estático para obtener el momento actual
of(...) Método estático para crear valores concretos
parse(String) / parse(String, formatter) Parsea desde texto
plus* / minus* / with* Operaciones que devuelven nuevos objetos
isBefore / isAfter / isEqual Comparación
Month / DayOfWeek Enumeraciones legibles para meses y días
Locale Para controlar el idioma de nombres de meses/días

13. Cuestionario de autoevaluación

Preguntas

  1. Nombra dos problemas del viejo Date / Calendar que java.time resuelve.

  2. ¿Qué clase usarías para representar la fecha de tu cumpleaños? ¿Y la hora a la que suena tu alarma? ¿Y la fecha y hora exactas de tu próxima cita médica?

  3. ¿Es LocalDate mutable o inmutable? Demuéstralo con un ejemplo de código.

  4. ¿Qué devuelve LocalDate.of(2026, 7, 14).getDayOfWeek()? ¿Qué tipo de dato devuelve?

  5. Escribe una línea que cree un LocalDate representando el 25 de diciembre de 2026 usando la enumeración Month.

  6. ¿Qué diferencia hay entre Period y Duration? Pon un ejemplo de uso para cada uno.

  7. Escribe el patrón DateTimeFormatter para formatear una fecha como "14/07/2026".

  8. ¿Qué patrón usarías para mostrar la fecha "martes, 14 de julio de 2026"?

  9. ¿Qué ocurre si llamas a LocalDate.parse("14-07-2026") sin pasar un DateTimeFormatter?

  10. ¿Por qué no es buena idea usar LocalDateTime para representar la hora de una reunión entre personas de distintos países?

  11. ¿Cómo se obtiene la fecha y hora actuales en Madrid usando ZonedDateTime?

  12. Si tienes un LocalDate fecha y escribes fecha.plusDays(5); sin asignar el resultado, ¿qué le pasa a fecha?

  13. ¿Qué clase usarías para almacenar la marca de tiempo de un log: LocalDateTime o Instant? ¿Por qué?

  14. ¿Qué método de LocalDate te dice cuántos días tiene el mes actual?

  15. ¿Cómo harías para que los nombres de meses salieran en español al formatear una fecha?

Soluciones

  1. Por ejemplo: (a) Date es mutable, lo que permite que un método modifique tu fecha sin que te enteres; java.time es inmutable. (b) Calendar numera los meses desde 0 (enero = 0), lo que genera bugs; java.time los numera de 1 a 12 y además ofrece la enumeración Month.

  2. Cumpleaños: LocalDate (no necesita hora ni zona). Alarma: LocalTime (no necesita fecha ni zona). Cita médica: LocalDateTime (necesita fecha y hora, pero normalmente no es internacional; si lo fuera, ZonedDateTime).

  3. Es inmutable. Ejemplo:

    1
    2
    3
    LocalDate f = LocalDate.of(2026, 7, 14);
    f.plusDays(1);          // el resultado se descarta
    System.out.println(f);  // sigue siendo 2026-07-14
    
    Para que el cambio surta efecto hay que asignar: f = f.plusDays(1);.

  4. Devuelve un valor de tipo DayOfWeek (enumeración), concretamente DayOfWeek.TUESDAY (martes). Para obtener un número (1-7, donde lunes es 1), usarías getDayOfWeek().getValue().

  5. LocalDate navidad = LocalDate.of(2026, Month.DECEMBER, 25);

  6. Period expresa intervalos en años, meses y días (apropiado para diferencias calendáricas entre LocalDate). Duration expresa intervalos en horas, minutos, segundos y nanosegundos (apropiado para diferencias absolutas entre instantes u horas).

  7. Period ejemplo: Period.between(nacimiento, hoy) para calcular la edad.
  8. Duration ejemplo: Duration.between(apertura, cierre) para calcular las horas de un turno.

  9. DateTimeFormatter.ofPattern("dd/MM/yyyy").

  10. "EEEE, d 'de' MMMM 'de' yyyy" con Locale español: DateTimeFormatter.ofPattern("EEEE, d 'de' MMMM 'de' yyyy", new Locale("es", "ES")).

  11. Se lanza DateTimeParseException porque el formato ISO por defecto de LocalDate es yyyy-MM-dd (con guiones en orden año-mes-día), no dd-MM-yyyy. Para parsear "14-07-2026" habría que crear un DateTimeFormatter con patrón "dd-MM-yyyy".

  12. Porque LocalDateTime no tiene información de zona horaria. Si una persona en Madrid y otra en México ven "reunión a las 17:00", cada una interpretará su hora local, y se conectarán a horas distintas. ZonedDateTime elimina la ambigüedad indicando explícitamente la zona.

  13. ZonedDateTime ahoraMadrid = ZonedDateTime.now(ZoneId.of("Europe/Madrid"));

  14. Nada. fecha sigue teniendo su valor original porque LocalDate es inmutable y el método plusDays devuelve un nuevo LocalDate que se descarta al no asignarlo.

  15. Instant, porque representa un instante preciso en UTC independiente de la zona horaria del servidor. Es lo que usan internamente los sistemas de logging y bases de datos para garantizar consistencia.

  16. lengthOfMonth().

  17. Pasando un Locale español al DateTimeFormatter:

    DateTimeFormatter f = DateTimeFormatter.ofPattern("d 'de' MMMM 'de' yyyy", new Locale("es", "ES"));
    System.out.println(fecha.format(f));   // 14 de julio de 2026
    

Evaluación del cuestionario

Aciertos Nivel
14-15 Dominas la sección. Adelante con la siguiente.
10-13 Bien, repasa los puntos fallados.
6-9 Repasa la sección, especialmente la diferencia Period vs Duration y los patrones de formateo.
Menos de 6 Vuelve a leer con calma y ejecuta el ejemplo integrador paso a paso en IntelliJ.

14. Información ampliada y próximos pasos

Próximos pasos:

  1. Practica en IntelliJ. Copia el ejemplo integrador, ejecútalo y modifícalo: cambia el evento por tu próximo examen, calcula los días que faltan, prueba a parsear tu fecha de nacimiento en formato dd/MM/yyyy y a imprimirla con formato largo en español.
  2. Experimenta con Period y Duration. Calcula cuántos días has vivido y cuántas horas. Verás que Period te da años/meses/días y Duration te da horas absolutas.
  3. Avanza a la Sección 7 de UD2. StringBuilder y concatenación eficiente: por qué concatenar strings con + en bucles es caro, cómo StringBuilder lo resuelve, y cómo usar StringJoiner y String.join para unir elementos con separadores. Es una sección corta y muy práctica, especialmente útil para construir reportes y mensajes con muchos fragmentos.