W3docs

Java LocalDate

Representa fechas sin hora ni zona horaria en Java con LocalDate: creación, manipulación y consultas.

LocalDate es una fecha de calendario — año, mes, día — sin hora del día ni zona horaria. Representa la misma fecha en cualquier reloj en cualquier lugar: cuando escribes LocalDate.of(2025, 11, 4), eso es el cuatro de noviembre en el calendario ISO, y punto. Sin 14:30 adjunto, sin desplazamiento UTC, sin ambigüedad Tokyo-vs-Honolulu.

Esto lo convierte en el tipo correcto para muchas cosas que el antiguo java.util.Date manejaba mal: cumpleaños, fechas de contratos, fechas de facturas, la fecha seleccionada por un selector de fechas en la interfaz. En cualquier lugar donde un día de calendario sea la unidad, LocalDate es la clase indicada.

Creación

Las tres fábricas estándar:

LocalDate today  = LocalDate.now();                          // system default zone
LocalDate stardate = LocalDate.of(2025, 11, 4);              // year, month (1-12), day (1-31)
LocalDate parsed = LocalDate.parse("2025-11-04");            // ISO-8601 yyyy-MM-dd

now() lee la fecha actual en la zona horaria predeterminada de la JVM. Eso es casi siempre lo que quieres; para pruebas es un problema, y las formas sobrecargadas con Clock (LocalDate.now(clock)) permiten inyectar un reloj fijo. El capítulo de análisis cubre parse con formatos personalizados; el predeterminado solo acepta fechas ISO-8601.

También puedes usar el enum Month en lugar de un entero 1..12:

LocalDate.of(2025, Month.NOVEMBER, 4);                       // type-safe; no risk of using 0 for January

Si alguna vez escribiste new GregorianCalendar(2025, 11, 4) y obtuviste diciembre (porque la API heredada usa meses basados en cero), la forma con enum es la mejora que necesitas.

Inspección

El catálogo de accesores:

int year      = date.getYear();
Month month   = date.getMonth();                              // enum
int monthVal  = date.getMonthValue();                         // 1-12
int day       = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek();                          // enum: MONDAY, TUESDAY, ...
int dayOfYear = date.getDayOfYear();                          // 1-366
boolean leap  = date.isLeapYear();
int monthLen  = date.lengthOfMonth();                         // 28-31
int yearLen   = date.lengthOfYear();                          // 365 or 366

Month y DayOfWeek son enums. Úsalos; hacen que el código que compara con un día o mes específico sea mucho más claro:

if (date.getDayOfWeek() == DayOfWeek.MONDAY) ...              // type-safe
if (date.getMonth() == Month.NOVEMBER) ...                    // no off-by-one risk

Cada enum tiene sus propios métodos auxiliares — Month.length(boolean leap), DayOfWeek.getValue() devuelve 1-7 con lunes = 1, y DayOfWeek.plus(7) para "el mismo día, n días a partir de ahora."

Modificación — cada método devuelve una nueva instancia

Los métodos aritméticos:

date.plusDays(7);                                              // a week later
date.plusWeeks(2);
date.plusMonths(1);                                            // careful: month length varies
date.plusYears(1);

date.minusDays(30);
date.minusYears(5);

Y las formas de "reemplazar un campo":

date.withYear(2026);
date.withMonth(1);
date.withDayOfMonth(1);
date.withDayOfYear(1);                                         // first day of the year

Todos estos devuelven un nuevo LocalDate. El original no se modifica. date.plusDays(7) y olvidarse de capturar el resultado es una operación sin efecto — y un error que todos hemos cometido al menos una vez.

La advertencia sobre "la longitud del mes varía" con plusMonths: cuando agregar un mes llevaría a un día que no existe en el mes destino, java.time lo recorta al último día. LocalDate.of(2025, 1, 31).plusMonths(1) es 2025-02-28 (o 02-29 en año bisiesto), no 2025-03-03. El comportamiento está documentado y es consistente, pero significa que plusMonths(1) y minusMonths(1) no siempre son inversos.

Comparación

date.isBefore(other);
date.isAfter(other);
date.isEqual(other);                                           // same as equals here; useful on ZonedDateTime
date.compareTo(other);                                         // -1 / 0 / +1

LocalDate implementa Comparable<LocalDate>, por lo que se ordena naturalmente en cualquier colección. Para "¿está esta fecha en [inicio, fin]?" la forma típica es !date.isBefore(start) && !date.isAfter(end).

Distancia: until y ChronoUnit.between

¿Cuántos días hay entre dos fechas?

long days = ChronoUnit.DAYS.between(start, end);               // a long; signed
long weeks = ChronoUnit.WEEKS.between(start, end);
long months = ChronoUnit.MONTHS.between(start, end);

Period diff = start.until(end);                                // a Period (years/months/days)

ChronoUnit.X.between es la llamada correcta para "¿cuántos X completos hay entre estas fechas?" until devuelve un Period, que es el desglose en forma de calendario — útil para "llevas 2 años, 3 meses y 14 días como miembro."

Nota la convención de signo: between(start, end) es positivo cuando end está después de start, negativo en caso contrario.

El atajo de "qué día de la semana es..."

El paquete de ajustadores temporales te proporciona los predicados que de otro modo calcularías a mano:

import static java.time.temporal.TemporalAdjusters.*;

date.with(firstDayOfMonth());
date.with(lastDayOfMonth());
date.with(firstDayOfNextMonth());
date.with(next(DayOfWeek.MONDAY));                             // next Monday strictly after `date`
date.with(nextOrSame(DayOfWeek.MONDAY));                       // today if today is Monday, else next
date.with(previousOrSame(DayOfWeek.SUNDAY));
date.with(lastInMonth(DayOfWeek.FRIDAY));                      // last Friday of the month

El capítulo sobre Ajustadores Temporales los cubre en profundidad. Por ahora, el punto clave: no escribas "el próximo lunes después de esta fecha" a mano; los ajustadores ya lo tienen resuelto.

Advertencia sobre la zona horaria

LocalDate no tiene zona horaria, por lo que LocalDate.now() tiene que elegir una para saber qué día del calendario es "ahora". El valor predeterminado es la zona predeterminada de la JVM (ZoneId.systemDefault()). Si estás ejecutando en un servidor configurado en UTC a las 23:30 hora local en Nueva York, LocalDate.now() devuelve la fecha de mañana desde la perspectiva de Nueva York — porque la zona de la JVM dice que ya pasó la medianoche UTC.

Para una fecha local a una zona conocida, pasa la zona explícitamente:

LocalDate tokyoToday = LocalDate.now(ZoneId.of("Asia/Tokyo"));

Esto afecta en producción exactamente cuando la laptop del desarrollador está en una zona diferente a la del servidor desplegado. Sé explícito cuando la zona importa.

Un ejemplo práctico: aritmética de fechas de facturas

El programa a continuación usa LocalDate para el tipo de trabajo que haría un pequeño sistema de facturación — generar una fecha de factura, calcular fechas de vencimiento, contar días pendientes, determinar el fin de mes y encontrar el próximo día hábil. Es la forma realista del código con LocalDate.

java— editable, runs on the server

Lo que se puede aprender de la ejecución:

  • LocalDate.of(2025, Month.NOVEMBER, 4) fue la forma segura. La sobrecarga con enteros (2025, 11, 4) funciona, pero el enum Month hace imposible usar 0 para enero — el error heredado de GregorianCalendar. Cuando el segundo argumento puede ser cualquiera de las dos formas, usa el enum.
  • plusDays(30) devolvió un LocalDate nuevo; imprimir el original al final del programa lo mostró sin cambios. Cada método aritmético y with* sigue esta regla, que es lo que hace que el tipo sea seguro para hilos por construcción. No se necesita copia defensiva; pasar un LocalDate a un método es siempre seguro.
  • La demostración de plusMonths(1) mostró el comportamiento de recorte: 31 de enero + 1 mes = 28 de febrero (o 29 en año bisiesto). El comportamiento está documentado y es consistente, pero jan31.plusMonths(1).minusMonths(1) devuelve January 28, no January 31. El viaje de ida y vuelta esperando recuperar el original funciona con plusDays/minusDays, no con plusMonths/minusMonths.
  • Los ajustadores temporales (lastDayOfMonth, firstDayOfNextMonth, nextOrSame(MONDAY)) reemplazaron los recorridos manuales del calendario en varias líneas. Encadenados, expresan "el primer lunes en o después del primero del mes siguiente" con dos ajustadores. El siguiente capítulo sobre LocalTime y el capítulo dedicado a los Ajustadores Temporales profundizan más.
  • ChronoUnit.DAYS.between(invoice, today) devolvió un long con signo. El complemento invoiceDate.until(today) devolvió un Period — con forma de calendario, con campos separados de año/mes/día. Los dos responden preguntas diferentes: ChronoUnit.X para "cuántos X completos," Period para "en forma amigable con el calendario." Elige el que mejor se ajuste a la salida que deseas.

Qué sigue

LocalDate era el lado de la fecha. El siguiente capítulo, Java LocalTime, es su espejo — la hora del día, sin fecha adjunta y sin zona horaria. La misma API fluida, una clase más pequeña, las mismas garantías de inmutabilidad.

Práctica

Práctica
`LocalDate.of(2025, 1, 31).plusMonths(1)` devuelve qué valor, ¿y por qué?
`LocalDate.of(2025, 1, 31).plusMonths(1)` devuelve qué valor, ¿y por qué?
Was this page helpful?