W3docs

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

Il formato stringa è PnYnMnDP1Y2M3D 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 days

Questo è 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 negative

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

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

Per "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) restituisce 567 (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."

java— editable, runs on the server

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?" usa ChronoUnit.YEARS.between(birth, today) >= 18, non age.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() è diventato Period.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 un Period tramite aritmetica e vuoi una rappresentazione "pulita," chiama normalized prima di memorizzarlo o visualizzarlo.
  • P1Y.equals(P12M) era false, e P1Y.equals(P365D) era anch'esso false. L'uguaglianza è strutturale, non basata sulla lunghezza. Applicato a 2024-01-31 (un anno bisestile), + P1Y e + P12M hanno entrambi dato 2025-01-31, ma + P365D ha dato 2025-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 con LocalDate.isEqual. La forma .normalized() risolve il caso anno/mese ma mai quello dei giorni.
  • La chiamata inst.plus(Period.ofMonths(1)) ha lanciato UnsupportedTemporalTypeException. Un Instant non ha calendario, quindi un mese (la cui lunghezza varia) non ha significato su di esso. La parte giorni di un Period va bene su un Instant — un giorno equivale a 86.400 secondi fissi — ma mesi e anni no. Converti tramite ZonedDateTime prima 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 (Duration su una LocalDate) è 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.

Esercitazione

Pratica
Un utente si registra il 31 gennaio 2025. Il codice di fatturazione calcola l'addebito successivo con `signupDate.plus(Period.ofMonths(1))`. Quale data è l'addebito successivo e cosa devi sapere sul comportamento?
Un utente si registra il 31 gennaio 2025. Il codice di fatturazione calcola l'addebito successivo con `signupDate.plus(Period.ofMonths(1))`. Quale data è l'addebito successivo e cosa devi sapere sul comportamento?
Was this page helpful?