mer 30 ottobre 2024 - Lo Sviluppatore  anno VI

Date & Time con Java 8 (parte I)

Condividi

 

L’uso di date e orari è fondamentale in molte applicazioni, basti pensare quanti siano i casi d’uso in cui un programmatore deve trattare date di nascita, periodi di noleggio, orari di apertura e chiusura di shop, timestamp, ecc..

Fin dalla prima versione java non ha mai avuto una buona API per la gestione di date e ore e, escludendo progetti di terze parti come Joda-Time, si è dovuto arrivare alla versione 8 per avere finalmente una API Date-Time robusta ed efficiente all’interno del core di java SE.

Un pò di storia

Quando uscì java 1, l’unica classe per trattare le date era la classe java.util.Date. Questa classe in realtà non rappresenta una data ma un intervallo di tempo espresso in millisecondi che va dal 1 gennaio 1970 al momento di creazione dell’oggetto Date,  inoltre l’istante temporale viene calcolato in base al default time della JVM e quindi i programmatori hanno pensato erroneamente che Date fosse in grado di gestire i fusi orari quando in relatà non era così.

Con la versione di java 1.2 è stato introdotta la classe java.util.Calendar ma la situazione non è migliorata di molto in quanto anche questa classe ha gli stessi problemi di Date e cioè:

  1. E’ mutabile, mentre una data dovrebbe essere auspicabilmente immutabile
  2. Entrambe rappresentano intervalli di tempo
  3. I mesi partono da 0, generando confusione
  4. Le classi di utilità per formattare le date si possono usare solo con Date e non con Calendar.
  5. Non sono thread-safe e quindi inadatte, senza gli adeguati accorgimenti, in applicazioni concorrenti.

Questi e altri problemi hanno portato alla creazione di API di terze parti, come la già accennata Joda-Time che si proponevano di ridurre questo gap nelle API standard di java. E’ proprio da questa API ben riuscita e largamente diffusa che java 8 ha attinto per la creazione delle nuove API  java.time.

Concetti chiave

La nuova API è stata sviluppata tenendo presente i seguenti punti chiave:

  1. Classi immutabili e che rapopresentano un valore (e non un intervallo temporale). Tutte le classi sono Thread-safe permettendone l’uso in ambiente concorrente csì come sono ed evitando quindi al programmatore di dover curare gli aspetti relativi alla concorrenza.
  2. Domain-driven design. Le API sono state sviluppate tenendo in mente vari use case in cui Date e Time verrano usate rendendo il tutto maggiormente chiaro e comprensibile.
  3. La nuova API consente di lavorare con diversi sistemi di calendario, al fine di supportare le esigenze degli utenti in alcune aree del mondo, come il Giappone o la Thailandia, che non necessariamente usano ISO-8601; e lo fa senza imporre oneri aggiuntivi a tutti gli sviluppatori che hanno bisogno di lavorare solo con i calendari standard.

La nuova API  java.time

La nuova API è composta dai seguenti package:

  • java.time – è il package che contiene le classi base
  • java.time.chrono – permette l’accesso a differenti tipi di calendari
  • java.time.format contiene le classi per la formattazione e il parsing di date e orari
  • java.time.temporal – estende il package base mettendo a disposizione classi per la manipolazione più a basso livello di date e orari.
  • java.time.zone – contiene le classi per la gestione delle time zones

Nella maggior parte dei casi d’uso i programmatori useranno le classi base, i formatter e in qualche occasione le estenzioni temporal e, nonostante siano stai introdotti 68 nuovi type le classi da usare nei casi più comune sono 3 o 4.

Ma vediamo ora in maggiore dettaglio le nuove classi e come queste vengono usate.

Gestire le Date

Una delle classe più importanti della nuova API è LocalDate. Questa classe è immutabile, rappresenta un valore e non dà alcuna informazione temporale. Il termine “locale” proviene dallo standard ISO-8601 che è lo stesso che viene adottato in Joda-Time. Questa classe rappresenta una descrizione di una data. come ad esempio “1 settembre 2014” che  avrà inizio in momenti diversi nella timeline in base alla nostra posizione sull terra; quindi, per dire, a Roma la stessa data locale inizierà 6 ore prima che a New York e 9 ore prima che a San Francisco.

Tutte le classi principali della nuova API sono costruiti con metodi factory abbastanza “parlanti”. In particolare quando si costruisce una data partendo dai sui campi (giorno, mese, anno) il nome del factory method si chiama of  mentre se si costruisce una data da un’altro oggetto si usa il metodo from. Inoltre ci sono dei metodi per costruire una data prendendo in input una stringa. Ma vediamo qualche esempio di creazione ed utilizzo di LocalDate.

LocalDate date = LocalDate.of(2014, Month.SEPTEMBER, 9);
int year = date.getYear(); // 2014
Month month = date.getMonth(); // SEPTEMBER
int dom = date.getDayOfMonth(); // 9
DayOfWeek dow = date.getDayOfWeek(); // Tuesday
int len = date.lengthOfMonth(); // 30 (Numero di gg di settembre)
boolean leap = date.isLeapYear(); //false (2014 non è bisestile)

 

In questo esempio, vediamo come una data viene creata utilizzando un metodo factory (tutti i costruttori sono private) e poi interrogata per estrarre alcune informazioni essenziali. Da notare la chiarezza dei nomi dei metodi che rendono il codice decisamente più leggibile.
Nel prossimo esempio, vediamo invece come l’istanza può essere manipolata. Poiché la classe è immutabile, ogni manipolazione restituisce una nuova istanza, mantenendo l’originale inalterata.

LocalDate date = LocalDate.of(2014, Month.SEPTEMBER, 10);
LocalDate date2 = date.withYear(2015); // 10-09-2015
date2 = date.plusMonths(2); // 10-10-2015
date2 = date.minusDays(1); // 09-09-201

 

Questi cambiamenti sono relativamente semplici, ma spesso si ha la necessità di apportare manipolazioni più complesse. Ci viene in aiuto per questo tipo di operazioni il package java.time.temporal ed in particolare la classe TemporalAdjuster che è una classe di utilità preconfezionata con vari metodi statici in grado di manipolare una data. Ci sono vari adjuster che permettono le manipolazioni più comuni sulle date ma è possibile implementare  adjuster personalizzati qualora servissero. Ma vediamo qualche esempio:

import static java.time.DayOfWeek.*
import static java.time.temporal.TemporalAdjusters.*

LocalDate date = LocalDate.of(2014, Month.SEPTEMBER, 10);
date = date.with(lastDayOfMonth());
date = date.with(nextOrSame(WEDNESDAY));

 

 Gestire gli orari

Oltre le date altro concetto importante sono le ore del giorno, rappresentatate in questa API dalla classe  LocalTime. Un classico esempio è quello di rappresentare l’orario di apertura di un negozio ad esempio 09:00 – 20:00. Similmente a LocalDate anche LocalTime rappresenta un valore senza alcuna data associata ne fuso orario ed è relativo ad una determinata zona del pianeta.
Utilizzare un LocalTime è simile al’uso di LocalDate:

LocalTime time = LocalTime.of(20, 30);
int hour = date.getHour(); // 20
int minute = date.getMinute(); // 30
time = time.withSecond(6); // 20:30:06
time = time.plusMinutes(3); // 20:33:06

 

Anche per LocalTime, come per le date, si possono usare i meccanismi degli adjusters per eseguire varie manipolazione degli orari.

Gestione congiunta di Data e Ora

E’ chiaramente possibile gestire data e ora in maniera congiunta e in questo entra in gioco un’ altra classe: la LocalDateTime. Questo tipo di valore è una semplice combinazione di LocalDate e LocalTime e rappresenta sia una data che un tempo senza fuso orario.
Un LocalDateTime viene creato direttamente o combinando una data e un orario:

LocalDateTime dt1 = LocalDateTime.of(2014, Month.JUNE, 10, 20, 30);
LocalDateTime dt2 = LocalDateTime.of(date, time);
LocalDateTime dt3 = date.atTime(20, 30);
LocalDateTime dt4 = date.atTime(time);

Da notare nel terzo e quarto esempio l’utilizzo di atTime(), che fornisce un modo fluente per costruire una data-ora. La maggior parte delle classi presenti nella API espongono metodi ‘”at” che permettono di creare oggetti più complessi unendo più oggetti date e/o time. Gli altri metodi su LocalDateTime sono simili a quelli di LocalDate e LocalTime. Questa uniformità nel modello dei metodi è molto utile per imparare ad usare rapidamente questa API.

Di seguito una tabella che riassume i vari prefissi usati nei nomi dei metodi e il loro significato:

Prefisso Descrizione
of Metodo factory statico per la creazione di un oggetto dalle sue componenti
from Metodo factory statico che prova ad estrarre un’istanza da un oggetto simile
 now Metodo factory statico che crea un istanza con orario impostato all’ora corrente
 parse Metodo factory statico che crea un istanza parsando una stringa passata in input
get Restituisce una parte costituente (giorno, mese,….) di un oggetto date-time
 is Verifica se una qualche proprietà di un oggetto date-time è vera o falsa.
 with Restituisce una copia di un oggetto date-time con qualche proprietà modificata.
 plus Restituisce una copia di un oggetto date-time con un valore di una qualche proprietà aumentata di un certo tot di tempo
 minus Restituisce una copia di un oggetto date-time con un valore di una qualche proprietà diminuita di un certo tot di tempo
 to Converte un oggetto date-time in un altro
 at Combina un oggetto date-time con altri oggetti per creare un’altro oggetto date-time più complesso
 format Formatta un oggetto date-time come stringa nel formato desiderato.

 

Vale la pena spendere due parole in più sul concetto di data-time come valore. I valori sono tipi di dati semplici in cui due istanze che sono uguali sono perfettamente sostituibili. In questi casi il concetto di identità (==)  ha poco senso. La classe String è l’ esempio canonico di un valore (value object):  due stringhe sono uguali se sono costiutiti dalla stessa sequenza di caratteri e il risultato del metodo  equals() restituisce true,  non ci importa sapere se sono o meno lo stesso identico oggetto usando l’operatore ==.
La maggior parte delle classi date-time sono anche esse valori per cui valgono le stesse regole e quindi non c’è alcun motivo di confrontare due oggetti date-time usando == (e il compilatore , nel caso, da un warning) ma invece va usato il metodo equals().

Il nuovo sistema dei Calendari

La classe LocalDate, così come la maggior parte delle classi della java.time API usano un unico calendario standard ed in particolare lo standard  ISO-8601.
Il sistema di calendario ISO-8601 è il sistema maggiormente usato al mondo ed è quello comunemente conosciuto sotto il nome di Calendario Gregoriano. Ma comuque al fine di supportare i programmatori che utilizzano dei sistemi non ISO la java.time API ha il proprio meccanismo di gestione dei vari tipi di calendario. L’interfaccia Chronology è la classe base su cui è costruito il sistema di gestione dei calendari e 4 sono gli altri sistemi di calendario forniti in Java SE 8: il buddista thailandese, il Minguo, il giapponese, e la Hira.
Ogni sistema di calendario ha una classe data dedicata, vi è quindi un ThaiBuddhistDate, MinguoDate, JapaneseDate, e HijrahDate. Queste classi sono usate nelle applicazioni altamente localizzate, come ad esempio potrebbe essere un’aplicazione per il governo giapponese.

Ci sono anche altre interfacce importanti che permettono di creare sistemi di calendario personalizzati. In particoalre abbiamo:

ChronoLocalDate
ChronoLocalDateTime
ChronoZonedDateTime

Queste classi comuqnue sono esclusivamente per gli sviluppatori che stanno lavorando su applicazioni altamente internazionalizzate che devono tenere conto dei sistemi di calendario locali, e non dovrebbero essere mai utilizzate dagli sviluppatori senza questi requisiti.

la classe Instant

Quando si tratta di date e orari, di solito pensiamo in termini di anni, mesi, giorni, ore, minuti, e secondi. Tuttavia, questo è solo un modello di tempo, quello che chiamano “umano”. Il secondo modello di uso comune  è il tempo “macchina” o tempo “continuo”.  Quest’ultimo tipo di modello è simile alla “vecchia” implementazione della calsse Date pre java 8.
L’API java.time fornisce una visione macchina del tempo tramite il tipo di valore istantaneo. Esso prevede la possibilità a rappresentare un punto sulla timeline senza
altre informazioni contestuali, come un fuso orario. Concettualmente, rappresenta semplicemente il numero di secondi dalla mezzanotte del 1 Gennaio 1970 UTC. Dal momento che l’API è sulla base nanosecondi, la classe Instant fornisce una  precisione nell’ordine dei nanosecondi. Esempi d’uso:

Instant start = Instant.now();
// qualche calcolo
Instant end = Instant.now();
assert end.isAfter(start);

 

La classe Instant viene tipicamente utilizzata per la memorizzazione e il confronto di  timestamp, in cui è necessario registrare quando si è verificato un certo evento, tuttavia  per quanto riguarda questa classe è meglio soffermarsii su cosa NON si può fare con che su cosa si può fare. Ad esempio, queste righe di codice lanciano un eccezione:

instant.get(ChronoField.MONTH_OF_YEAR);
instant.plus(6, ChronoUnit.YEARS);

 

Queste righe danno eccezione perchè Istant rappresenta solo un numero di secondi e nanosecondi e non ha alcuna “consapevolezza” delle unità temporali “umane” quali possono essere mesi o anni.

Conclusioni

Abbiamo visto come la API java.time rende più semplice e intuitivo lavorare con le date e gli orari. In un prossimo articolo approfondiremo l’uso di queste API mostrando altre  interessanti feature che ci mette a disposizione la java.time, come ad esempio la gestione dei fusi orari.

 

Riferimenti

Java SE 8 Date and Time

Intuitive, Robust Date and Time Handling, Finally Comes to Java

Java™ Platform Standard Ed. 8

 

 

One thought on “Date & Time con Java 8 (parte I)

Lascia un commento

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.

Top