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-ddnow() 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 JanuarySe 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 366Month 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 riskOgni 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 yearOgnuno 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 / +1LocalDate 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 monthIl 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.
Cosa ricavare dall'esecuzione:
LocalDate.of(2025, Month.NOVEMBER, 4)era la forma sicura. L'overload con intero (2025, 11, 4) funziona, ma l'enumMonthrende impossibile usare 0 per gennaio — l'errore legacy diGregorianCalendar. Quando il secondo argomento può essere l'uno o l'altro, usa l'enum.plusDays(30)ha restituito un nuovoLocalDate; la stampa dell'originale alla fine del programma lo mostrava intatto. Ogni metodo aritmetico ewith*segue questa regola, il che rende il tipo thread-safe per costruzione. Non è necessaria alcuna copia difensiva; passare unLocalDatea 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, majan31.plusMonths(1).minusMonths(1)restituisceJanuary 28, nonJanuary 31. Il round-trip con ritorno all'originale funziona perplusDays/minusDays, non perplusMonths/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 unlongcon segno. Il metodo companioninvoiceDate.until(today)ha restituito unPeriod— con forma di calendario, con campi separati per anno/mese/giorno. I due rispondono a domande diverse:ChronoUnit.Xper "quanti X interi,"Periodper "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à.