Vai al contenuto

Come manipolare il formato data/ora in Javascript

Uno dei principali crucci degli informatici è quello di gestire le date. Ebbene si, nonostante la maggior parte della popolazione mondiale usi il calendario gregoriano come calendario di riferimento per la vita quotidiana, per quanto possa sembrare strano, esistono diversi formati per rappresentare una data, tant’è che sono stati definiti diversi standard per dare un senso a quanto rappresentato.

In italia usiamo generalmente il formato gg/mm/aaaa. Ad esempio oggi è il 02/04/2021, dove il 02 rappresenta il giorno del mese, 04 rappresenta il mese di aprile e 2021 è l’anno. Tutto molto semplice, ma solo perchè siamo stati abituati a pensare così usando questo formato.

In alcuni luoghi come Cina, Corea e Iran, questo ordine viene invertito in aaaa/mm/gg e vabbè, ci può stare. Gli Stati Uniti invece sono uno dei pochi paesi che utilizzano la disposizione mm/gg/aaaa, che è davvero particolare! Quindi la data di oggi, in formato americano, si scriverebbe 04/02/2021 (che per un italiano sarebbe il 4 di febbraio!)

Ma perché gli americani hanno scelto di scrivere prima il mese? In realtà ci sono solo varie ipotesi, ma non si sa come questa pratica sia iniziata; una delle ipotesi è quella che i coloni americani abbiano preso in prestito il formato dal Regno Unito (che veniva usato prima del XX secolo) e che poi sia rimasto invariato nel tempo. Ciò che è sicuro è che i cittadini americani usano tale formato da quando esistono gli Stati Uniti, difatti nella dichiarazione d’indipendenza si trova scritto in alto a caratteri cubitali “July 4, 1776”. C’è da dire che – soprattutto nei contesti formali – non è appropriato utilizzare una forma puramente numerica della data poiché non risulta comprensibile ai lettori al di fuori degli Stati Uniti. Si tende, invece, scrivere il nome del mese per intero: oggi verrebbe scritto Aprile 4, 2021.

Little-Endian, Middle-Endian, Big-Endian

Endian (endianness) si riferisce all’organizzazione dell’archiviazione di dati binari in cui il byte più significativo viene generalmente memorizzato per primo (nell’indirizzo più piccolo, a sinistra) o per ultimo (nell’indirizzo più grande, a destra). Se memorizzato per primo, viene chiamato big-endian e per ultimo viene chiamato little-endian. Quando si tratta di byte, la prima cifra (a sinistra) è solitamente la più significativa e avrà il valore più grande (ad esempio, il numero 1234, l’1 è rappresentato nel byte 1000).

Intel e Digital usano il formato little endian mentre Motorola, IBM e Sun usano il formato big endian. Il formato dei dati contenuti nelle intestazioni dei protocolli di rete è anch’esso big endian; altri esempi di uso di questi due diversi formati sono quello del bus PCI, che è little endian, o quello del bus VME che è big endian.

È interessante notare che i il termine endian, è stato preso in prestito da I viaggi di Gulliver di Jonathan Swift (1726). Nel capitolo 4, Gulliver apprende che i lillipuziani e i blefuscuani sono divisi sino al fratricidio da un’annosa e irresolubile controversia sul modo più corretto di rompere le uova, se dalla parte più grossa (i grossapuntisti o big-endian) o da quella più piccola (i strettapuntisti o little-endian).

A Lilliput, per editto dell’imperatore – il cui figlio una volta si tagliò aprendo un uovo dall’estremità più grande – fu ordinato di aprire le uova dall’estremità più corta (little endians); a Blefuscu si rifugiarono gli oppositori che volevano conservare la tradizione di rompere le uova dall’estremità più grande (big endians). A causa di questa differenza e della sua legittimazione imperiale ne seguì una sanguinosa guerra tra i lillipuziani sostenitori del big-endian e i little-endian che seguivano l’imperatore (nel mettere in scena questa stupida guerra delle uova, Swift ha satirizzato nel suo romanzo le guerre civili che infuriavano tra protestanti e cattolici durante il XVI e il XVII secolo e più in particolare le loro “profonde” cause).

Per le date, simpaticamente, viene adottato questo concetto. Tuttavia il formato americano della data non segue nessuna delle due regole, infastidendo gran parte degli informatici del pianeta, e quindi è stato coniato un nuovo termine: middle-endian.

  • Little-endian (cioè giorno/mese/anno)
    • sono date valide 02/04/2021 e 02 aprile 2021
  • Middle-endian (cioè mese/giorno/anno)
    • sono date valide 04/02/2021 e Aprile 02, 2021
  • Big-endian (cioè anno/mese/giorno)
    • sono date valide 2021/04/02 e 2021 Aprile 02 (anche se quest’ultima è veramente molto rara da vedere)

ISO 8601

Per non causare una nuova guerra civile, venne creato l’ISO 8601 (Data elements and interchange formats – Information interchange – Representation of dates and times) che è uno standard internazionale per la rappresentazione di date e orari. La necessità di uno standard nasce dal fatto che la data formulata come 04-09-03 può indicare il:

  • 4 settembre 2003 (o addirittura 1903) in Europa e altri Paesi
  • 9 aprile 2003 negli Stati Uniti d’America
  • 3 settembre 2004 secondo lo standard ISO 8601

Come si può facilmente capire questa incertezza può creare molti problemi nelle comunicazioni internazionali.

La gestione delle date in Javascript

Gli sviluppatori Javascript sanno che manipolare date non è cosi semplice come si pensa. Oltre ai sopracitati problemi di formati data e ora, bisogna considerare le differenze di fuso orario e localizzazione. Proprio per questo motivo in Javascript sono presenti delle librerie sviluppati da terzi (prima tra tutti moment.js) che aiutano a gestire al meglio le date nelle proprie applicazioni. Sebbene queste librerie riducano la complessità dell’attività, avere una chiara comprensione della gestione delle date in Javascript ha i suoi vantaggi.

L’oggetto Date

L’oggetto Date in Javascript è l’elemento principale quando si tratta di gestire data e ora. La data viene memorizzata come un numero espresso in millisecondi trascorsi dal 1 gennaio 1970 00:00:00 (UTC). Questa combinazione di data e ora è nota come epoch time (qui un utile converter). Per quanto riguarda Javascript, la data 1 gennaio 1970 rappresenta l’anno 0, ovvero l’inizio di tutto.

Per creare un oggetto di tipo Date, basta istanziare in una variabile l’oggetto, passando la data come stringa.

const date = new Date("2020-12-31");

Se si prova a stampare a video la data creata, questo sarà il risultato:

Thu Dec 31 2020 01:00:00 GMT+0100 (Central European Standard Time)

Oltre alla data che abbiamo passato, l’oggetto Date ha più valori, inclusi un’ora e un fuso orario. Poiché non abbiamo fornito un valore specifico per questi parametri durante la creazione dell’oggetto, Javascript utilizza l’ora locale e il fuso orario del sistema. Se vogliamo passare l’ora o il fuso orario, possiamo usare il seguente formato:

YYYY-MM-DDTHH:mm:ss.sssZ

Il formato utilizzato in queste stringhe è il formato esteso del calendario ISO 8601, dove (attenzione alle maiuscole/minuscole):

YYYY: anno
MM: mese (1 to 12)
DD: giorno (1 to 31)
HH: ora in formato H24 (0 to 23)
mm: minuti (0 to 59)
ss: secondi (00 to 59)
sss: millisecondi (0 to 999)
T: usato per separare la data e l’ora
Z: se presente, la data viene considerata nel fuso orario UTC (che corrisponde al meridiano di Greenwich). Altrimenti, viene considerata  secondo il fuso orario locale.

Tuttavia, se T e Z non sono presenti, la data di creazione della stringa potrebbe dare risultati diversi in browser diversi. In tal caso, per avere sempre lo stesso fuso orario per la data, aggiungi +HH:mmo -HH:mm alla fine.

newDate = new Date("2021-09-23T23:45Z");
// Fri Sep 24 2021 01:45:00 GMT+0200 (Central European Summer Time)

newDate = new Date("2021-09-23T23:45");
// Thu Sep 23 2021 23:45:00 GMT+0200 (Central European Summer Time)

newDate = new Date("2021-09-23T23:45+05:30");
// Thu Sep 23 2021 20:15:00 GMT+0200 (Central European Summer Time)

Per istanziare un nuovo oggetto, possiamo anche passare al costruttore i singoli elementi della data, ad esempio così:

newDate = new Date(1998, 9, 30, 13, 40, 05);
//Fri Oct 30 1998 13:40:05 GMT+0100 (Central European Standard Time)

Cosa c’è di strano? Quando abbiamo creato la data, abbiamo usato per il mese il valore 9, che per noi rappresenta il mese di settembre. Tuttavia, quando stampiamo il risultato, il mese è ottobre. Perché? Javascript parte da 0 per identificare ogni mese in un anno. Quindi gennaio è rappresentato da 0 anziché da 1. Allo stesso modo, ottobre è rappresentato da 9 anziché da 10. In questo metodo di creazione di una data, non possiamo passare un argomento per indicare il suo fuso orario. Quindi, l’impostazione predefinita è l’ora locale del sistema.

Possiamo anche istanziare una data passando il timestamp, cioè il valore numerico che rappresenta i millesimi trascorsi dal 1970, in questo modo:

newDate = new Date(1223727718982);
// Sat Oct 11 2008 14:21:58 GMT+0200 (Central European Summer Time)

Se invece vogliamo conoscere la data corrente del sistema, basta istanziare un oggetto di tipo Date senza passare nessun parametro al costruttore, così:

now = new Date()
// Sat Jan 09 2021 22:06:33 GMT+0100 (Central European Standard Time)

Formattazione delle date

Javascript fornisce diverse funzioni integrate per formattare una data. Vediamo come funziona ogni funzione di formattazione.

// definiamo una nuova data
let newDate = new Date("2021-01-09T14:56:23")

newDate.toString()
// "Sat Jan 09 2021 14:56:23 GMT+0100 (Central European Standard Time)"

newDate.toDateString()
// "Sat Jan 09 2021"

newDate.toLocaleDateString()
// "1/9/2021"

newDate.toLocaleTimeString()
// "2:56:23 PM"

newDate.toLocaleString()
// "1/9/2021, 2:56:23 PM"

newDate.toGMTString()
// "Sat, 09 Jan 2021 13:56:23 GMT"

newDate.toUTCString()
// "Sat, 09 Jan 2021 13:56:23 GMT"

newDate.toISOString()
// "2021-01-09T13:56:23.000Z"

newDate.toTimeString()
// "14:56:23 GMT+0100 (Central European Standard Time)"

newDate.getTime()
// 1610200583000

API di internazionalizzazione

L’API ECMAScript per l’internazionalizzazione consente la formattazione di una data in una specifica locale utilizzando l’oggetto Intl.

let newDate = new Date("2021-01-09T14:56:23")

//formattazione secondo le impostazioni predefinite del computer locale
Intl.DateTimeFormat().format(newDate)
// "1/9/2021"

//formattazione secondo un locale specifico, ad esempio de-DE (Germania)
Intl.DateTimeFormat("de-DE").format(newDate)
// "9.1.2021"

È possibile passare un oggetto options alla funzione DateTimeFormat per visualizzare i valori dell’ora e personalizzare l’output.

let options = {
    year: "numeric",
    month: "long",
    weekday: "long",
    hour: "numeric",
    minute: "numeric",
}

Intl.DateTimeFormat("en-US", options).format(newDate)
// "January 2021 Saturday, 2:56 PM"

Formati di data personalizzati

Se invece si desidera formattare la data in qualsiasi altro formato oltre a quello fornito dalle funzioni standard di Javascript, sarà possibile farlo accedendo a ciascuna parte della data separatamente e combinandole. Javascript infatti mette a disposizione delle funzioni per “estrarre” i singoli componenti della data e memorizzarli in delle variabili. Ad esempio:

newDate.getFullYear() // 2021 
newDate.getMonth() // 0 (parte da 0) 
newDate.getDate() // 9 
newDate.getDay() // 6 (parte da 0 che rappresenta Domenica) 
newDate.getHours() // 14 
newDate.getMinutes() // 56 
newDate.getUTCHours() // 9 
newDate.getUTCDate() // 9

Memorizzando  dati in singole variabili, possiamo creare qualsiasi formato personalizzato abbiamo in mente.

Aggiornare le date

Per modificare e aggiornare una data esistente, basta utilizzare i metodi messi a disposizione per manipolare i singoli componenti della data. Ad esempio:

newDate = new Date("2021-01-08T22:45:23") 

newDate.setYear(1998) 
//Thu Jan 08 1998 22:45:23 GMT+0100 (Central European Standard Time) 

newDate.setMonth(4) 
//Fri May 08 1998 22:45:23 GMT+0200 (Central European Summer Time) 

newDate.setDate(12) 
//Tue May 12 1998 22:45:23 GMT+0200 (Central European Summer Time) 

newDate.setHours(12) 
newDate.setMinutes(21) 
newDate.setUTCDate(26) 
newDate.setUTCMinutes(56)

Confrontare le date

Per sapere se una data specifica precede un’altra, puoi utilizzare direttamente gli operatori maggiore e minore di per il confronto.

let prima_data = new Date(2010, 3, 19) 
let seconda_data = new Date(2010, 3, 24) 

prima_data > seconda_data
//false

Tuttavia, se si desidera verificarne l’uguaglianza, né l’operatore == né === funziona come previsto.

prima_data = new Date(2009, 12, 23) 
seconda_data = new Date(2009, 12, 23) 
console.log(prima_data == seconda_data) // false 
console.log(prima_data === seconda_data) // false

Invece si dovrà recuperare il timestamp di ogni data e confrontarli con l’operatore di uguaglianza.

first.getTime() === second.getTime() // true

Questo perché le date in JavaScript sono oggetti, quindi ogni data ha un’istanza diversa della classe e l’operatore == oppure === confronta l’indirizzo di memoria invece dei valori effettivi delle date.