Java Period
Rappresenta quantità di date (anni, mesi, giorni) in Java con Period.
Period è il fratello basato sul calendario di Duration. Mentre Duration rappresenta "X secondi più Y nanosecondi," Period rappresenta "X anni, Y mesi, Z giorni." È il tipo giusto per qualsiasi durata espressa in termini di calendario: "periodo di prova di 30 giorni," "abbonamento annuale," "periodo di preavviso di due mesi," "aggiunge un ciclo di fatturazione alla data di rinnovo."
I due non si mescolano mai. Duration.ofDays(30) equivale esattamente a 30 × 24 × 3600 secondi. Period.ofDays(30) rappresenta 30 giorni di calendario, che di solito — ma non sempre — equivale a 30 × 24 ore (le transizioni DST aggiungono o rimuovono un'ora). Per "esattamente quei secondi," usa Duration. Per "il giorno di calendario che si trova N giorni dopo," usa Period.
Creazione
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]DIl formato stringa è PnYnMnD — P1Y2M3D corrisponde a un anno, due mesi, tre giorni. Il prefisso P è obbligatorio. Nessuna T (che indicherebbe una Duration); nessuna ora, minuto o secondo (non rientrano in questo tipo).
Period.between(start, end) accetta due LocalDate e restituisce una scomposizione della differenza:
Period age = Period.between(LocalDate.of(1990, 3, 15), LocalDate.of(2025, 11, 4));
// P35Y7M20D — 35 years, 7 months, 20 daysQuesto è l'idioma standard per "calcolare un'età." Il risultato è una scomposizione, non un numero singolo — ci sono 35 anni, poi 7 mesi aggiuntivi, poi 20 giorni. Per ridurre a un singolo conteggio, usa ChronoUnit.YEARS.between(...), che restituisce un long.
Ispezione
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 negativeTre accessor per i tre componenti, più toTotalMonths per un'aggregazione rapida. Non esiste toTotalDays — quello richiederebbe di conoscere il contesto di calendario (un anno ha 365 o 366 giorni; un mese ha 28-31 giorni).
Aritmetica
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() è interessante: comprime qualsiasi conteggio di mesi pari o superiore a 12 in anni. Period.of(0, 14, 0).normalized() diventa Period.of(1, 2, 0). I giorni non vengono toccati — non esiste un "normalizza 31 giorni in 1 mese e 1 giorno" perché i mesi non hanno lunghezza costante.
Aggiunta a una data
Period è un TemporalAmount. Qualsiasi Temporal simile a una data lo accetta:
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));Aggiungere la parte mese o anno di un Period a un Instant lancia UnsupportedTemporalTypeException — un Instant è un punto sulla linea del tempo senza calendario, quindi il JDK si rifiuta di calcolare "un istante un mese dopo" senza un fuso orario. (La parte dei giorni va bene: Instant.plus(Period.ofDays(1)) ha successo, perché il JDK tratta un giorno esattamente come 86.400 secondi. Sono solo mesi e anni che non hanno lunghezza fissa e quindi non hanno significato su un Instant.) Quando hai bisogno di aritmetica di calendario, converti tramite ZonedDateTime:
Instant nextMonth = inst.atZone(ZoneId.of("UTC"))
.plus(Period.ofMonths(1))
.toInstant();È una verbosità voluta — la conversione è il punto in cui si fornisce il contesto di calendario mancante.
La regola di clamping di plusMonths da LocalDate si applica all'aritmetica con Period nello stesso modo: 31 gennaio + Period.ofMonths(1) è il 28 febbraio, non il 3 marzo.
Period non normalizza tra componenti
Un comportamento sottile: Period.of(1, 0, 365) non è uguale a Period.of(2, 0, 0), anche se descrivono la stessa durata quando aggiunti a una data tipica. La classe memorizza la scomposizione così com'è e confronta per struttura:
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)); // truePer "questo periodo è almeno un anno indipendentemente da come è scomposto," confronta sulle date: start.plus(p1).isEqual(start.plus(p2)) è l'unica verifica completamente corretta.
Distanza: 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);I due rispondono a domande diverse:
Period.between(start, end)restituisce "1 anno, 6 mesi, 14 giorni" — utile quando vuoi visualizzare una scomposizione.ChronoUnit.DAYS.between(start, end)restituisce567(o qualunque sia il numero effettivo di giorni) — utile quando vuoi confrontare o accumulare.
Usa il secondo quando devi fare aritmetica sul risultato. Usa il primo quando vuoi mostrare qualcosa a un utente.
Un esempio pratico: abbonamenti, prove e età
Il programma seguente usa Period per un piccolo scenario di abbonamento: la prova termina un mese dopo l'iscrizione, la data di rinnovo si ripete ogni anno, l'età del cliente è calcolata dalla data di nascita e il comportamento di clamping ai confini del mese è reso esplicito. Mostra anche il contrasto con Duration per "lo stesso intervallo in tempo trascorso."
Cosa ricavare dall'esecuzione:
- Aggiungere
Period.ofMonths(1)al 31 gennaio ha prodotto il 28 febbraio — la stessa regola di clamping di LocalDate.Period.plusMonths(1).minusMonths(1)non è sempre l'identità. Quando si calcolano date di fatturazione vicine alla fine del mese, progetta esplicitamente attorno al clamping (ad esempio, fattura sempre il 1° del mese successivo) invece di presumere la simmetria di andata e ritorno. Period.between(birth, today)ha restituito una scomposizione di calendario — anni, mesi, giorni. Per "sono adulti?" usaChronoUnit.YEARS.between(birth, today) >= 18, nonage.getYears() >= 18. Entrambi danno la stessa risposta in questo caso, ma rispondono a domande diverse in generale —ChronoUnit.YEARS.betweenè il totale degli anni interi,age.getYears()è la componente anni della scomposizione.Period.of(0, 14, 0).normalized()è diventatoPeriod.of(1, 2, 0). Il conteggio dei giorni non è stato toccato — i giorni non possono essere normalizzati senza sapere quali mesi sono coinvolti. Se costruisci unPeriodtramite aritmetica e vuoi una rappresentazione "pulita," chiamanormalizedprima di memorizzarlo o visualizzarlo.P1Y.equals(P12M)erafalse, eP1Y.equals(P365D)era anch'essofalse. L'uguaglianza è strutturale, non basata sulla lunghezza. Applicato a2024-01-31(un anno bisestile),+ P1Ye+ P12Mhanno entrambi dato2025-01-31, ma+ P365Dha dato2025-01-30— un giorno in meno, perché il 2024 ha 366 giorni. Quindi anche "la stessa lunghezza" dipende dalla data a cui la applichi. Quando vuoi davvero sapere "questi due periodi producono la stessa data finale?", calcola entrambe le date finali e confronta conLocalDate.isEqual. La forma.normalized()risolve il caso anno/mese ma mai quello dei giorni.- La chiamata
inst.plus(Period.ofMonths(1))ha lanciatoUnsupportedTemporalTypeException. UnInstantnon ha calendario, quindi un mese (la cui lunghezza varia) non ha significato su di esso. La parte giorni di unPeriodva bene su unInstant— un giorno equivale a 86.400 secondi fissi — ma mesi e anni no. Converti tramiteZonedDateTimeprima quando hai bisogno di aritmetica di calendario; il sistema di tipi ti obbliga a scegliere esplicitamente un fuso orario. Il caso speculare del capitolo Duration (Durationsu unaLocalDate) è lo stesso design: il JDK si rifiuta di inventare il contesto mancante.
Cosa c'è dopo
Period chiude la coppia "durate di tempo." I prossimi due capitoli trattano il confine stringa ↔ valore: Java Date Formatting per convertire i valori java.time in stringhe, e Java Date Parsing per l'operazione inversa. Entrambi utilizzano DateTimeFormatter, che è il sostituto moderno e thread-safe del legacy SimpleDateFormat.