W3docs

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]D

La forma en string es PnYnMnDP1Y2M3D 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 days

Ese 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 negative

Tres 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 years

normalized() 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));  // true

Para "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) devuelve 567 (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."

java— editable, runs on the server

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 usar ChronoUnit.YEARS.between(birth, today) >= 18, no age.getYears() >= 18. Ambos dan la misma respuesta en este caso, pero responden preguntas diferentes en general — ChronoUnit.YEARS.between es el total de años completos, age.getYears() es el componente de años del desglose.
  • Period.of(0, 14, 0).normalized() se convirtió en Period.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 un Period desde aritmética y quieres una representación "limpia," llama a normalized antes de almacenar o mostrar.
  • P1Y.equals(P12M) fue false, y P1Y.equals(P365D) también fue false. La igualdad es estructural, no por longitud. Aplicados a 2024-01-31 (un año bisiesto), + P1Y y + P12M dieron ambos 2025-01-31, pero + P365D dio 2025-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 con LocalDate.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. Un Instant no tiene calendario, así que un mes (cuya longitud varía) no tiene significado en él. La parte de días de un Period está bien en un Instant — un día son exactamente 86,400 segundos — pero los meses y años no. Convierte a través de ZonedDateTime primero 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 (Duration sobre un LocalDate) 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.

Práctica

Práctica
Un usuario se registra el 31 de enero de 2025. Tu código de facturación calcula el siguiente cargo con `signupDate.plus(Period.ofMonths(1))`. ¿Cuál es la fecha del siguiente cargo y qué debes saber sobre el comportamiento?
Un usuario se registra el 31 de enero de 2025. Tu código de facturación calcula el siguiente cargo con `signupDate.plus(Period.ofMonths(1))`. ¿Cuál es la fecha del siguiente cargo y qué debes saber sobre el comportamiento?
Was this page helpful?