Fechas y Horas con java.time
LocalDate, LocalTime y LocalDateTime representan fechas sin zona horaria; ZonedDateTime e Instant representan un momento exacto. Todas son inmutables. Duration mide tiempo en segundos y Period en días, meses y años.
En una línea Instant.now(), LocalDate.of(2026, 10, 28).plusDays(30), Duration.between(inicio, fin).toMillis().
public class Vencimientos {
private final Clock reloj;
public Vencimientos(Clock reloj) { this.reloj = reloj; }
public Instant fechaLimite(Duration plazo) {
return Instant.now(reloj).plus(plazo); // momento exacto, en UTC
}
public String paraUsuario(Instant momento, ZoneId zona) {
return DateTimeFormatter.ofPattern("d 'de' MMMM, HH:mm", Locale.forLanguageTag("es"))
.format(momento.atZone(zona)); // se convierte solo al mostrar
}
}
// En el test: tiempo fijo y resultado reproducible
var reloj = Clock.fixed(Instant.parse("2026-10-28T10:00:00Z"), ZoneOffset.UTC);
var v = new Vencimientos(reloj);
assertThat(v.fechaLimite(Duration.ofHours(48)))
.isEqualTo(Instant.parse("2026-10-30T10:00:00Z"));
assertThat(v.paraUsuario(Instant.parse("2026-10-28T10:00:00Z"), ZoneId.of("America/Mexico_City")))
.isEqualTo("28 de octubre, 04:00");
Cómo funciona por dentro
java.time separa dos ideas que Date mezclaba: el momento exacto y su representación humana. Instant es un punto en la línea del tiempo (segundos y nanosegundos desde 1970 en UTC): ideal para registrar cuándo ocurrió algo. LocalDate, LocalTime y LocalDateTime son fechas y horas «de calendario» sin zona: un cumpleaños, la hora de apertura de una tienda. ZonedDateTime y OffsetDateTime unen una fecha local a una zona o a un desfase, y sí representan un momento exacto.
Todas las clases son inmutables y seguras entre hilos: plusDays devuelve un objeto nuevo. Duration mide intervalos en segundos (útil para timeouts), Period en unidades de calendario (un mes no siempre tiene los mismos días) y DateTimeFormatter convierte a texto y desde texto.
Para poder probar código que depende de la hora, no llames a Instant.now() directamente: recibe un Clock por constructor. En producción inyectas Clock.systemUTC() y en los tests Clock.fixed(...), y el resultado deja de depender del momento en que se ejecuta el test.
java.time sustituye a Date y Calendar, que eran mutables y confusos.
LocalDateTime para un evento global: pierdes la zona horaria. Para momentos exactos usa Instant u OffsetDateTime.
Se conecta con Instant en APIs RESTZonas horariasClock en los tests
Compruébalo: ¿Qué tipo usarías para la fecha de nacimiento de un cliente?
LocalDate. Es una fecha de calendario que no depende de la zona horaria: alguien nacido el 3 de mayo cumple años el 3 de mayo esté donde esté.