W3docs

Java LocalDateTime

Combina data e ora senza informazioni sul fuso orario in Java con LocalDateTime.

LocalDateTime è LocalDate e LocalTime uniti insieme — una data del calendario e un orario del giorno, ancora senza fuso orario. È il valore naturale per "questo evento è avvenuto alle 14:30 del 4 novembre," ed è il tipo java.time più usato nelle codebase reali che non gestiscono utenti globali.

L'API fluente ha la stessa forma delle due metà — le stesse factory now/of/parse, gli stessi modificatori plusX/minusX/withX, gli stessi confronti isBefore/isAfter. Le parti interessanti sono quelle nuove: la combinazione con una data o un orario, la decomposizione inversa, e la conversione esplicita a ZonedDateTime quando il fuso orario diventa rilevante.

Creazione

LocalDateTime now    = LocalDateTime.now();                  // JVM default zone
LocalDateTime made   = LocalDateTime.of(2025, 11, 4, 14, 30);
LocalDateTime made2  = LocalDateTime.of(2025, Month.NOVEMBER, 4, 14, 30, 15);
LocalDateTime made3  = LocalDateTime.of(LocalDate.of(2025, 11, 4), LocalTime.of(14, 30));
LocalDateTime parsed = LocalDateTime.parse("2025-11-04T14:30:00");   // ISO-8601

La forma ISO-8601 ha una T letterale tra la data e l'orario — è il separatore standard. LocalDateTime.parse("2025-11-04 14:30:00") (spazio invece di T) non viene interpretato con il parser predefinito; occorrerebbe un DateTimeFormatter personalizzato.

Decomposizione

LocalDate date = dt.toLocalDate();
LocalTime time = dt.toLocalTime();

Questi metodi sono l'inverso di LocalDate.atTime(time) / LocalTime.atDate(date). Entrambi sono pura proiezione — nessuna perdita di informazioni, nessun fuso orario introdotto.

Tutti gli accessor in una volta

LocalDateTime eredita il menu di accessor da entrambe le metà:

dt.getYear();          dt.getMonth();        dt.getMonthValue();
dt.getDayOfMonth();    dt.getDayOfWeek();    dt.getDayOfYear();
dt.getHour();          dt.getMinute();       dt.getSecond();          dt.getNano();

Stessi nomi, stessa semantica. Non è necessario chiamare toLocalDate() o toLocalTime() per accedere ai singoli campi — sono tutti disponibili direttamente.

Aritmetica oltre il confine

La differenza cruciale rispetto a LocalTime: LocalDateTime.plusHours(3) alle 23:00 non fa un wrap silenzioso. Avanza al giorno successivo:

LocalDateTime late = LocalDateTime.of(2025, 11, 4, 23, 0);
late.plusHours(3);   // 2025-11-05T02:00 — date advanced as expected

Questo è il motivo principale per usare LocalDateTime invece di LocalTime per qualsiasi calcolo che potrebbe attraversare la mezzanotte. La matematica è coerente con ciò che ci si aspetta da un orologio reale che conosce il giorno corrente.

dt.plusDays(7);          dt.plusHours(36);        dt.plusMinutes(150);
dt.minusYears(1);        dt.minusSeconds(45);

dt.withYear(2026);       dt.withHour(0);          dt.withMinute(0);

La regola di clamping di plusMonths da LocalDate si applica anche qui: LocalDateTime.of(2025, 1, 31, 12, 0).plusMonths(1) è 2025-02-28T12:00, non 2025-03-03T12:00. Il clamp riguarda solo la componente data; l'orario rimane invariato.

Il fuso orario è assente intenzionalmente

Un LocalDateTime non è un momento sulla linea temporale globale. LocalDateTime.of(2025, 11, 4, 9, 0) potrebbe essere le 9 del mattino a New York, le 9 a Berlino, o le 9 a Tokyo — tre Instant molto diversi, e LocalDateTime non indica quale. Se due LocalDateTime sono uguali, significa che le stringhe di data e ora sono uguali; ciò non significa che i momenti sottostanti siano uguali.

Questa è una caratteristica, non un bug. Per "il contratto è firmato alle 14:00 ora locale ovunque si trovi il firmatario," LocalDateTime è esattamente la forma giusta. Per "il server ha ricevuto la richiesta alle...", è la forma sbagliata — usa Instant. Per "la riunione inizia alle 14:00 ora di New York," sbagliata di nuovo — usa ZonedDateTime.

Per convertire a un momento con fuso orario, occorre aggiungere il fuso esplicitamente:

ZonedDateTime ny = ldt.atZone(ZoneId.of("America/New_York"));
Instant       inst = ldt.atZone(ZoneId.systemDefault()).toInstant();

atZone(...) è la chiamata fondamentale — è il momento in cui il sistema dei tipi ti costringe a decidere quale fuso intendi. Una volta deciso, la conversione a Instant è meccanica. I prossimi due capitoli (ZonedDateTime, Instant) trattano in dettaglio le forme con zona e globale.

Confronto

dt.isBefore(other);
dt.isAfter(other);
dt.isEqual(other);
dt.compareTo(other);

L'ordinamento è lessicografico per (date, time). Lo stesso avvertimento di prima: due LocalDateTime vengono confrontati per le loro rappresentazioni stringa di data e ora, non per i momenti sottostanti — perché senza un fuso orario non esistono momenti sottostanti.

Distanza

ChronoUnit.X.between funziona direttamente:

long minutes = ChronoUnit.MINUTES.between(start, end);
long days = ChronoUnit.DAYS.between(start, end);
Duration d = Duration.between(start, end);

Duration.between funziona su LocalDateTime (funziona su qualsiasi Temporal). Per l'aritmetica puramente calendaria — "quanti mesi tra questi due LocalDateTime" — usa ChronoUnit.MONTHS.between, che restituisce un long, oppure Period.between(start.toLocalDate(), end.toLocalDate()) per la scomposizione in formato calendario.

Un esempio pratico: pianificazione oltre la mezzanotte

Il programma seguente usa LocalDateTime per un piccolo pezzo di codice di pianificazione: un turno notturno che inizia alle 22:00 e termina alle 06:00, calcolando correttamente la durata attraverso la mezzanotte; arrotondando "adesso" al quarto d'ora successivo; trovando la prossima occorrenza di una riunione ricorrente alle 09:30; e dimostrando la regola che il fuso orario deve essere aggiunto esplicitamente quando si converte a un momento temporale.

java— editable, runs on the server

Cosa ricavare dall'esecuzione:

  • Duration.between(startShift, endShift) ha prodotto PT8H. Il turno ha attraversato la mezzanotte e le componenti della data hanno gestito il riporto — nessuna ambiguità. Lo stesso calcolo su semplici LocalTime avrebbe restituito PT-16H (il problema di LocalTime descritto nel capitolo precedente). Per l'aritmetica che potrebbe attraversare la mezzanotte, LocalDateTime è il tipo corretto.
  • Partendo da 20:00, plusHours(3) è rimasto sull'11-04 (23:00, nessun attraversamento della mezzanotte); plusHours(5) ha avanzato a 11-05T01:00. La famiglia plus/minus su LocalDateTime propaga i riporti correttamente attraverso l'intera catena A/M/G/h/m/s/ns. Nessuna gestione speciale richiesta nel codice.
  • Il blocco "prossima riunione alle 09:30" ha costruito le 09:30 di oggi con tre chiamate withX, poi ha scelto tra oggi e domani in base a isBefore. Questa è la forma comune per "il prossimo evento ricorrente a quest'orario del giorno" — abbastanza piccola da mettere inline, abbastanza comune da estrarre in un helper se ne hai molti.
  • Il blocco "stesso LocalDateTime, fusi diversi" ha prodotto due Instant diversi con sei ore di differenza. Questa è la ragione centrale per cui LocalDateTime non pretende di essere un momento. La classe rifiuta di far credere che una data e un orario da soli siano informazioni sufficienti; è un'etichetta su un orologio da muro da qualche parte, e quale orologio dipende dal fuso che fornisci.
  • Il controllo finale dell'immutabilità ha mostrato now invariato dopo plusDays(7).withHour(0).withMinute(0). Questa garanzia vale su ogni operazione, ogni catena, ogni helper — non c'è modo di mutare un LocalDateTime. Passalo liberamente, condividilo tra thread, memorizzalo in una Map.

Cosa viene dopo

LocalDateTime è l'ultimo dei tre tipi "Local" — nessun fuso, nessuna pretesa di essere un momento. Il prossimo capitolo, Java ZonedDateTime, aggiunge il fuso esplicitamente: un LocalDateTime più un ZoneId più l'offset risolto per quell'ora locale in quel fuso, che insieme identificano un momento effettivo sulla linea temporale globale.

Esercitati

Pratica
Due sviluppatori — uno a New York, uno a Berlino — costruiscono entrambi il valore `LocalDateTime.of(2025, 11, 4, 14, 0)`. Si tratta dello stesso momento nel tempo?
Due sviluppatori — uno a New York, uno a Berlino — costruiscono entrambi il valore `LocalDateTime.of(2025, 11, 4, 14, 0)`. Si tratta dello stesso momento nel tempo?
Was this page helpful?