dom 30 aprile 2017 - Lo Sviluppatore  anno III

Date & Time con java 8 (parte II)

Condividi

 

Abbiamo visto nel precedente articolo come la nuova API java.time di java 8 semplifica notevolmente il lavoro dei programmatori che devono trattare con date e orari. Abbiamo visto le classi base e il loro uso comune; in questo articlo proseguimo con la descrizione della nuova API parlando delle altre interessanti feature che ci vengono messe a disposizione, tra cui la gestione dei fusi orari.

bigbenTime Zones

Le classi base della API  LocalDateLocalTime, LocalDateTime, di cui abbiamo parlato nella prima parte di questo articolo, astraggono dalla complessità introdotta dai fusi orari. Un fuso orario è un insieme di regole, che corrisponde ad una zona della terra in cui il tempo standard è la stesso. Ci sono circa 40 fusi orari e sono definiti dal loro offset dal Coordinated Universal Time (UTC).

Prima dell’API java.time, si utilizzava la classe TimeZone per rappresentare i fusi orari.
Ora si utilizza la classe ZoneId. Esiste un ZoneId per ogni regione del pianeta. Ogni ZoneId corrisponde ad alcune regole che definiscono il fuso orario per quella località. Nell’sesempio che segue vediamo come creare un date-time e un Istant relativi al fuso orario italiano:

LocalDateTime dateTime = .....
// Si specifica lo ZoneId al momento della creazione dell'oggetto ZonedDateTime
ZoneId idz = ZoneId.of("Europe/Rome");
ZonedDateTime zdt1 = ZonedDateTime.of(dateTime, idz);

Instant instant = Instant.now();
ZonedDateTime zdt2 = instant.atZone(idz);

Comunemente quando si parla di fuso orario si parla di un offset fisso rispetto a UTC / Greenwich che è considerato lo zero nella divisone settoriale di ogni zona della terra.Ad esempio diciamo che New York è cinque ore indietro (quindi offset -5) rispetto a Londra che sta nello zero. La API java.time mette a diposizone la classe ZoneOffset (sottoclasse di ZoneId) che rappresenta l’offset di tempo dal meridiano zero di Greenwich a Londra. Di seguito un esempio dell’offset relativo all’Italia:

ZoneOffset offset = ZoneOffset.of("+1:00");

Come sviluppatore, sarebbe bello non dover trattare con i fusi orari e la loro complessità. Ma in molti casi non se ne può fare a meno e le API java.time ci vengono molto in aiuto. E’ sempre preferibile, utilizzare la LocalDateLocalTime, LocalDateTime, e la classe Instant, ma quando non si può evitare l’uso dei fusi orari, la classe ZonedDateTime è quella che fa al caso nostro.

ZonedDateTime rappresenta una data, completa di ora e  fuso orario (vedi esempio sotto). In generale possiamo dire che,  quando vogliamo rappresentare una data e un ora che non si basino sul server specifico è necessario utilizzare ZonedDateTime.

ZonedDateTime.parse("2007-12-03T10:15:30+01:00[Europe/Rome]");

Uno dei problemi più fastidiosi quando si lavora con le time zone è la gestione dell’ora legale (DTS). Tipicamente, in molti Paesi del mondo, si sposta l’orologio di un ora avanti in primavera per poi tornare nuovamente indietro in autunno. Questi cambiamenti sono gestiti da java.time come transizioni di offset. Per esempio, l’aggiunta di un giorno, sarà un aggiunta di un giorno logico che  può essere più o meno di 24 ore a secondo che ci troviamo in una zona in cui si applica l’ora legale o meno. Allo stesso modo, il metodo atStartOfDay() è così chiamato perché non si può assumere che la risultante tempo sarà mezzanotte ma ci potrebbe essere un gap da DST che porterebbe il risultato alle 01:00 anzichè a mezzanotte.

ZoneId zone = ZoneId.of(“Europe/Rome”);
LocalDate date = LocalDate.of(2014, Month.SEPTEMBER, 10);
ZonedDateTime zdt = date.atStartOfDay(zone);

Facciamo un esempio che ci fa vedere un caso d’uso delle ZonedDateTime e dei fusi orari : Supponiamo di dover rappresentare data e ora di partenza e di arrivo di un aereo che vola da San Francisco a Roma :

DateTimeFormatter format = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");

// Partenza da San Francisco il 20 luglio 2014, alle 19:30
LocalDateTime leaving = LocalDateTime.of(2014, Month.JULY, 20, 19, 30);
ZoneId leavingZone = ZoneId.of("America/Los_Angeles"); 
ZonedDateTime departure = ZonedDateTime.of(leaving, leavingZone);

try {
 String out1 = departure.format(format);
 System.out.printf("PARTENZA: %s (%s)%n", out1, leavingZone);

} catch (DateTimeException exc) {
 System.out.printf("%s can't be formatted!%n", departure);
 throw exc;
}

// Il volo dura 15 ore e 45 minuti, quindi 945 minuti
ZoneId arrivingZone = ZoneId.of("Europe/Rome"); 
ZonedDateTime arrival = departure.withZoneSameInstant(arrivingZone)
 .plusMinutes(945);

try {
 String out2 = arrival.format(format);
 System.out.printf("ARRIVO: %s (%s)%n", out2, arrivingZone);
} catch (DateTimeException exc) {
 System.out.printf("%s can't be formatted!%n", arrival);
 throw exc;
}

// Informazioni circa l'ora legale
if (arrivingZone.getRules().isDaylightSavings(arrival.toInstant())) 
 System.out.printf(" (a %s è attiva l'ora legale)%n",
 arrivingZone);
else
 System.out.printf(" ( a %s è attiva l'ora solare standard)%n",
 arrivingZone);

L’output del precedente pezzo di codice è il seguente:

PARTENZA:  20/07/2014  19:30 (America/Los_Angeles)
ARRIVO: 21/07/2014  14:15 (Europe/Rome)
(a Europe/Rome è attiva l'ora legale.)

Tempo come quantità

Le classi viste finora rappresentano un punto specifico nella timeline ma esistono anche due classi valore per rappresentare periodi di tempo, come può essere ad esempio “tre mesi e 1 giorno”, che invece è una distanza sulla timeline. Parliamo della classe Duration  che rappresenta un intervallo di tempo espresso in secondi e nanosecondi (esempio 25,4 secondi) e della classe Period che invece rappresenta un intervallo di tempo espresso in anni, mesi e giorni (esempio: 1 anno 3 mesi e 2 giorni). Queste classi posono essere usate per sommare o sottrare dei periodi di tempo dalle normali classi date-time:

// un oggetto duration che rappresenta 3 secondi e 5 nanosecondi
Duration duration = Duration.ofSeconds(3, 5);
Duration oneDay = Duration.between(today, yesterday);

// un oggetto period che rappresenta 6 mesi
Period sixMonths = Period.ofMonths(6);

LocalDate date = LocalDate.now();
LocalDate future = date.plus(sixMonths);

 Formattazione e parsing

In java.time abbiamo un intero package dedicato alla formattazione e stampa di date e orari: il java.time.format. Le classi cardine di questo package sono DateTimeFormatter e il suo relativo builder DateTimeFormatterBuilder.  E’ possibile creare un formattatore in tre modi:

  1. Usando i metodi statici e le costanti predefinite di DateTimeFormatter, come può essere ISO_LOCAL_DATE
  2. Usando i pattern del tipo dd/MM/yyyy
  3. Usando gli stili locali che posso essere in formato completo, lungo, medio o corto.
// Creazione formatter e formattazione mediante costante
DateTimeFormatter f1 = DateTimeFormatter.ISO_LOCAL_DATE;
LocalDate date = LocalDate.now();
String str = date.format(f1) // formatta la data nel formato ISO (es: 2014-12-03) 

// Creazione formatter, parsing e formattazione mediante pattern
DateTimeFormatter f2 = DateTimeFormatter.ofPattern(“dd/MM/yyyy”);
date = LocalDate.parse(“24/06/2014”, f2);
String str = date.format(f2);

// Creazione formatter e formattazione mediante stili locali
DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
LocalDate date = LocalDate.now();
String str = date.format(f); //es. Jan 12, 1952

Se c’è di bisogno di avere un maggiore controllo sui formatter è possibile creare questi con uno specifico Locale, Chronology, ZoneId e DecimalStyle:

Il metodo withLocale restituisce un nuovo formattatore con lo specifico Locale passato. Questo ci permette di definire formati specifici ad una particolare zona. 

Il metodo withChronology restituisce un nuovo formattatore in cui viene sostituito il sistema calendario.

Il metodo withZone restituisce un nuovo formattatore in cui viene sostituita la zona

Il metodo withDecimalStyle restituisce un nuovo formattatore che sostituisce il formato relativo ai decimali. I simboli DecimalStyle sono usati sia per la formattazione che per il parsing.

E’ possibile anche costrutire dei formatter personalizzati più complessi, qualora se ne avesse la necessità. In questo caso la classe DateTimeFormatterBuilder è quella che fa al caso nostro.

Altre feature della API java.time

La nuova API supporta diversi gradi di precisione, cioè è possibile approssimare un valore date-time alla precisione richiesta dal caso d’uso. Questo tipo di operazione si fa mediate il metodo truncatedTo esisteche consente di troncare un valore a un campo, come mostrato nel seguente esempio:

LocalTime truncatedTime = time.truncatedTo(ChronoUnit.SECONDS);

 

Java SE 8 dispone anche di classi per alcuni altri casi di uso comune. C’è la classe MonthDay, che contiene la coppia mese-giorno ed è utile per rappresentare ad esempio i compleanni, abbiamo anche la classe YearMonth che invece memorizza una coppia mese-anno, utile ad esempio, per indicare una scadenza di una carta di credito o comunque tutti quei casi in cui non serve specificare un giorno specifico.
JDBC in Java SE 8 supporterà questi nuovi tipi, ma non ci saranno cambiamenti nelle API pubbliche JDBC: sarà sufficiente usare i già noti metodi setObject e getObject.
Questi tipi possono essere mappati in tipi di database specifici (dipendenti dalla piattaforma) o ai tipi standard ANSI SQL; per esempio, la mappatura ANSI è la seguente:

ANSI SQL Java SE 8
DATE LocalDate
TIME LocalTime
TIMESTAMP LocalDateTime
TIME WITH TIMEZONE OffsetTime
TIMESTAMP WITH TIMEZONE OffsetDateTime

Conclusioni

Con questo articolo abiamo concluso la carrellata sulla nuova API java.time iniziata in un prededente articolo. La nuova API java.time è davvero molto completa e copre largamente tutti i casi d’uso più comuni (e non solo)  per la gestione di date e orari in tutto il mondo. Era da tanto che noi programmatori java aspettavamo una API così!

 

Riferimenti

Java SE 8 Date and Time

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

Java™ Platform Standard Ed. 8

 

Lascia un commento

Top