Java Period
Representa cantidades de tiempo basadas en calendario (años, meses, días) en Java con Period.
Period es el hermano basado en calendario de Duration. Mientras que Duration es "X segundos más Y nanosegundos," Period es "X años, Y meses, Z días." Es el tipo correcto para cualquier duración que se expresaría en términos de calendario: "período de prueba de 30 días," "suscripción anual," "preaviso de dos meses," "agregar un ciclo de facturación a la fecha de renovación."
Los dos nunca se mezclan. Duration.ofDays(30) son exactamente 30 × 24 × 3600 segundos. Period.ofDays(30) son 30 días calendarios, que generalmente (pero no siempre) equivalen a 30 × 24 horas (las transiciones de horario de verano añaden o restan una hora). Para "exactamente esa cantidad de segundos," usa Duration. Para "el día calendario que está N días después," usa Period.
Creación
Period.ofDays(30);
Period.ofWeeks(2); // stored as 14 days
Period.ofMonths(3);
Period.ofYears(1);
Period.of(1, 6, 0); // 1 year, 6 months, 0 days
Period.between(startDate, endDate); // takes LocalDate (not LocalDateTime)
Period.parse("P1Y2M3D"); // ISO-8601: P[years]Y[months]M[days]DLa forma en string es PnYnMnD — P1Y2M3D es un año, dos meses, tres días. El prefijo P es obligatorio. No hay T (eso lo convertiría en Duration); tampoco horas, minutos ni segundos (no encajan aquí).
Period.between(start, end) toma dos LocalDates y devuelve un desglose de la diferencia:
Period age = Period.between(LocalDate.of(1990, 3, 15), LocalDate.of(2025, 11, 4));
// P35Y7M20D — 35 years, 7 months, 20 daysEse es el modismo estándar para "calcular una edad." El resultado es un desglose, no un único número — hay 35 años completos, luego 7 meses adicionales, y luego 20 días. Para colapsar a un único conteo, usa ChronoUnit.YEARS.between(...), que devuelve un long.
Inspección
Period p = Period.of(1, 6, 14);
p.getYears(); // 1
p.getMonths(); // 6
p.getDays(); // 14
p.toTotalMonths(); // 1 * 12 + 6 = 18 (years + months, ignoring days)
p.isZero(); // false
p.isNegative(); // true if any component is negativeTres accesores para los tres componentes, más toTotalMonths para una agregación rápida. No existe toTotalDays — eso requeriría conocer el contexto de calendario (un año tiene 365 o 366 días; un mes tiene entre 28 y 31 días).
Aritmética
p.plus(Period.ofMonths(1));
p.plusYears(1);
p.plusMonths(6);
p.plusDays(14);
p.minus(Period.ofDays(7));
p.multipliedBy(3);
p.negated();
p.normalized(); // collapse extra months into yearsnormalized() es interesante: colapsa cualquier cantidad de meses mayor o igual a 12 en años. Period.of(0, 14, 0).normalized() resulta en Period.of(1, 2, 0). No toca los días — no existe "normalizar 31 días en 1 mes y 1 día" porque los meses no tienen una longitud constante.
Agregar a una fecha
Period es un TemporalAmount. Cualquier Temporal similar a una fecha lo acepta:
LocalDate maturity = LocalDate.of(2025, 11, 4).plus(Period.ofMonths(6));
LocalDate retirement = LocalDate.of(1990, 3, 15).plus(Period.ofYears(65));
LocalDateTime renewal = LocalDateTime.of(2025, 11, 4, 9, 0).plus(Period.ofYears(1));
ZonedDateTime nextBill = zdt.plus(Period.ofMonths(1));Agregar la parte de mes o año de un Period a un Instant lanza UnsupportedTemporalTypeException — un Instant es un punto en la línea de tiempo sin calendario, por lo que el JDK se niega a calcular "un instante un mes después" sin una zona horaria. (La parte de los días está bien: Instant.plus(Period.ofDays(1)) tiene éxito, porque el JDK trata un día como exactamente 86,400 segundos. Solo los meses y años no tienen una longitud fija y, por tanto, no tienen significado en un Instant.) Cuando necesitas aritmética de calendario, convierte a través de ZonedDateTime:
Instant nextMonth = inst.atZone(ZoneId.of("UTC"))
.plus(Period.ofMonths(1))
.toInstant();Esa verbosidad es intencional — la conversión es el lugar donde proporcionas la información de calendario que faltaba.
La regla de ajuste de plusMonths de LocalDate se aplica a la aritmética con Period de la misma manera: 31 de enero + Period.ofMonths(1) es el 28 de febrero, no el 3 de marzo.
Period no normaliza entre componentes
Un comportamiento sutil: Period.of(1, 0, 365) no es igual a Period.of(2, 0, 0), aunque describan la misma duración cuando se aplican a una fecha típica. La clase almacena el desglose tal como está y compara por estructura:
Period.of(1, 0, 365).equals(Period.of(2, 0, 0)); // false
Period.of(0, 14, 0).equals(Period.of(1, 2, 0)); // false (until normalized())
Period.of(0, 14, 0).normalized().equals(Period.of(1, 2, 0)); // truePara "este período es al menos un año independientemente de cómo esté desglosado," compara sobre fechas: start.plus(p1).isEqual(start.plus(p2)) es la única verificación completamente correcta.
Distancia: Period.between vs ChronoUnit.between
Period diff = Period.between(start, end); // calendar breakdown
long days = ChronoUnit.DAYS.between(start, end); // single long
long months = ChronoUnit.MONTHS.between(start, end);
long years = ChronoUnit.YEARS.between(start, end);Los dos responden preguntas diferentes:
Period.between(start, end)devuelve "1 año, 6 meses, 14 días" — útil cuando quieres mostrar un desglose.ChronoUnit.DAYS.between(start, end)devuelve567(o la cantidad literal de días) — útil cuando quieres comparar o acumular.
Usa el segundo cuando necesites hacer aritmética sobre el resultado. Usa el primero cuando quieras mostrar información a un usuario.
Un ejemplo práctico: suscripciones, pruebas y edades
El programa a continuación usa Period para un pequeño escenario de suscripción: la prueba termina un mes después del registro, la fecha de renovación se repite cada año, la edad del cliente se calcula a partir de su fecha de nacimiento, y el comportamiento de ajuste en los límites de mes se hace explícito. También muestra el contraste con Duration para "el mismo intervalo en tiempo transcurrido."
Lo que se puede aprender de la ejecución:
- Agregar
Period.ofMonths(1)al 31 de enero produjo el 28 de febrero — la misma regla de ajuste de LocalDate.Period.plusMonths(1).minusMonths(1)no siempre es la identidad. Cuando calcules fechas de facturación cerca del fin de mes, diseña teniendo en cuenta el ajuste explícitamente (p. ej., factura siempre el 1 del mes siguiente) en lugar de asumir simetría de ida y vuelta. Period.between(birth, today)devolvió un desglose de calendario — años, meses, días. Para "¿es mayor de edad?" conviene usarChronoUnit.YEARS.between(birth, today) >= 18, noage.getYears() >= 18. Ambos dan la misma respuesta en este caso, pero responden preguntas diferentes en general —ChronoUnit.YEARS.betweenes el total de años completos,age.getYears()es el componente de años del desglose.Period.of(0, 14, 0).normalized()se convirtió enPeriod.of(1, 2, 0). El conteo de días no se modificó — los días no pueden normalizarse sin saber qué meses están implicados. Si construyes unPerioddesde aritmética y quieres una representación "limpia," llama anormalizedantes de almacenar o mostrar.P1Y.equals(P12M)fuefalse, yP1Y.equals(P365D)también fuefalse. La igualdad es estructural, no por longitud. Aplicados a2024-01-31(un año bisiesto),+ P1Yy+ P12Mdieron ambos2025-01-31, pero+ P365Ddio2025-01-30— un día menos, porque 2024 tiene 366 días. Entonces, incluso "la misma longitud" depende de la fecha a la que se aplica. Cuando realmente quieres saber "¿producen estos dos períodos la misma fecha final?", calcula ambas fechas finales y compara conLocalDate.isEqual. La forma.normalized()corrige el caso año/mes pero nunca el caso de los días.- La llamada
inst.plus(Period.ofMonths(1))lanzóUnsupportedTemporalTypeException. UnInstantno tiene calendario, así que un mes (cuya longitud varía) no tiene significado en él. La parte de días de unPeriodestá bien en unInstant— un día son exactamente 86,400 segundos — pero los meses y años no. Convierte a través deZonedDateTimeprimero cuando necesites aritmética de calendario; el sistema de tipos te obliga a elegir una zona explícitamente. El fallo equivalente del capítulo de Duration (Durationsobre unLocalDate) es el mismo diseño: el JDK se niega a inventar el contexto que falta.
Qué sigue
Period cierra el par de "longitudes de tiempo." Los dos próximos capítulos cubren la frontera string ↔ valor: Java Date Formatting para convertir valores de java.time en strings, y Java Date Parsing para la operación inversa. Ambos funcionan con DateTimeFormatter, que es el reemplazo moderno y seguro para hilos del legado SimpleDateFormat.