W3docs

Java LocalDate

Rappresenta date senza ora o fuso orario in Java con LocalDate — creazione, manipolazione e interrogazione.

LocalDate è una data del calendario — anno, mese, giorno — senza ora del giorno e senza fuso orario. Rappresenta la stessa data su qualsiasi orologio ovunque: quando scrivi LocalDate.of(2025, 11, 4), si tratta del quattro novembre nel calendario ISO, punto. Nessun 14:30 allegato, nessun offset UTC, nessuna ambiguità Tokyo-vs-Honolulu.

Questo la rende il tipo corretto per molte cose che il legacy java.util.Date gestiva male: compleanni, date contrattuali, date di fatture, la data selezionata da un date-picker dell'interfaccia utente. Ovunque un giorno di calendario sia l'unità di misura, LocalDate è la classe giusta.

Creazione

Le tre factory standard:

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() legge la data corrente nel fuso orario predefinito della JVM. È quasi sempre quello che si vuole; per i test è un problema, e le forme con overload Clock (LocalDate.now(clock)) consentono di iniettare un clock fisso. Il capitolo sul parsing tratta parse con formati personalizzati; il predefinito accetta solo date ISO-8601.

Puoi anche usare l'enum Month invece di un intero 1..12:

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

Se hai mai scritto new GregorianCalendar(2025, 11, 4) e ottenuto dicembre (perché le API legacy usano mesi a base zero), la forma con enum è l'aggiornamento che cerchi.

Ispezione

Il catalogo degli accessor:

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 e DayOfWeek sono enum. Usali; rendono il codice che confronta un giorno o mese specifico notevolmente più chiaro:

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

Ogni enum ha i propri metodi di supporto — Month.length(boolean leap), DayOfWeek.getValue() che restituisce 1-7 con lunedì = 1, e DayOfWeek.plus(7) per "lo stesso giorno, n giorni da ora."

Modifica — ogni metodo restituisce una nuova istanza

I metodi aritmetici:

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);

E le forme "sostituisci un campo":

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

Ognuno di questi restituisce un nuovo LocalDate. L'originale rimane invariato. date.plusDays(7) dimenticando di catturare il risultato è un no-op — e un bug che tutti abbiamo scritto almeno una volta.

La nota sul "la lunghezza del mese varia" per plusMonths: quando aggiungere un mese porterebbe a un giorno che non esiste nel mese di destinazione, java.time tronca all'ultimo giorno. LocalDate.of(2025, 1, 31).plusMonths(1) è 2025-02-28 (o 02-29 in un anno bisestile), non 2025-03-03. Il comportamento è documentato e coerente, ma significa che plusMonths(1) e minusMonths(1) non sono sempre inversi l'uno dell'altro.

Confronto

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>, quindi si ordina naturalmente in qualsiasi collezione. Per "questa data è in [inizio, fine]?" la forma tipica è !date.isBefore(start) && !date.isAfter(end).

Distanza: until e ChronoUnit.between

Quanti giorni ci sono tra due date?

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 è la chiamata giusta per "quanti X interi ci sono tra questi?" until restituisce un Period, che è la scomposizione a forma di calendario — utile per "sei membro da 2 anni, 3 mesi, 14 giorni."

Nota la convenzione del segno: between(start, end) è positivo quando end è dopo start, negativo altrimenti.

La scorciatoia "che giorno della settimana è..."

Il pacchetto temporal-adjusters fornisce i predicati che altrimenti dovresti calcolare manualmente:

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

Il capitolo sui Temporal Adjusters li tratta in profondità. Per ora, il messaggio chiave è: non scrivere "il prossimo lunedì dopo questa data" a mano; gli adjuster lo hanno già.

Nota sul fuso orario

LocalDate non ha un fuso orario, quindi LocalDate.now() deve sceglierne uno per sapere quale giorno di calendario è "ora". Il predefinito è il fuso orario predefinito della JVM (ZoneId.systemDefault()). Se stai eseguendo su un server impostato su UTC alle 23:30 ora locale di New York, LocalDate.now() restituisce la data di domani dal punto di vista di New York — perché il fuso orario della JVM indica che è già passata la mezzanotte UTC.

Per una data locale in un fuso orario noto, passa esplicitamente il fuso orario:

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

Questo problema si manifesta in produzione esattamente quando il laptop dello sviluppatore si trova in un fuso orario diverso da quello del server distribuito. Sii esplicito quando il fuso orario è rilevante.

Un esempio pratico: aritmetica delle date di fatturazione

Il programma seguente usa LocalDate per il tipo di lavoro che farebbe un piccolo sistema di fatturazione — genera una data di fattura, calcola le date di scadenza, conta i giorni arretrati, determina la fine del mese e trova il prossimo giorno lavorativo. È la forma realistica del codice LocalDate.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • LocalDate.of(2025, Month.NOVEMBER, 4) era la forma sicura. L'overload con intero (2025, 11, 4) funziona, ma l'enum Month rende impossibile usare 0 per gennaio — l'errore legacy di GregorianCalendar. Quando il secondo argomento può essere l'uno o l'altro, usa l'enum.
  • plusDays(30) ha restituito un nuovo LocalDate; la stampa dell'originale alla fine del programma lo mostrava intatto. Ogni metodo aritmetico e with* segue questa regola, il che rende il tipo thread-safe per costruzione. Non è necessaria alcuna copia difensiva; passare un LocalDate a un metodo è sempre sicuro.
  • Il demo di plusMonths(1) ha mostrato il comportamento di troncamento: 31 gennaio + 1 mese = 28 febbraio (o 29 in un anno bisestile). Il comportamento è documentato e coerente, ma jan31.plusMonths(1).minusMonths(1) restituisce January 28, non January 31. Il round-trip con ritorno all'originale funziona per plusDays/minusDays, non per plusMonths/minusMonths.
  • Gli temporal adjusters (lastDayOfMonth, firstDayOfNextMonth, nextOrSame(MONDAY)) hanno sostituito le walkthrough manuali del calendario su più righe. Concatenati, esprimono "il primo lunedì di pari o dopo il primo del mese prossimo" in due adjuster. Il prossimo capitolo su LocalTime e il capitolo dedicato ai Temporal Adjusters approfondiscono l'argomento.
  • ChronoUnit.DAYS.between(invoice, today) ha restituito un long con segno. Il metodo companion invoiceDate.until(today) ha restituito un Period — con forma di calendario, con campi separati per anno/mese/giorno. I due rispondono a domande diverse: ChronoUnit.X per "quanti X interi," Period per "in forma calendario-friendly." Scegli quello la cui forma corrisponde all'output che vuoi.

Cosa c'è dopo

LocalDate era il lato data. Il prossimo capitolo, Java LocalTime, è il suo speculare — ora del giorno, senza data allegata e senza fuso orario. Stessa API fluente, classe più piccola, stesse garanzie di immutabilità.

Esercitazione

Pratica
`LocalDate.of(2025, 1, 31).plusMonths(1)` restituisce quale valore, e perché?
`LocalDate.of(2025, 1, 31).plusMonths(1)` restituisce quale valore, e perché?
Was this page helpful?