La guida definitiva alla manipolazione di DateTime
Pubblicato: 2022-03-11Come sviluppatore di software, non puoi scappare dalla manipolazione della data. Quasi tutte le app create da uno sviluppatore avranno alcuni componenti in cui la data e l'ora devono essere ottenute dall'utente, archiviate in un database e visualizzate all'utente.
Chiedi a qualsiasi programmatore della loro esperienza nella gestione di date e fusi orari e probabilmente condivideranno alcune storie di guerra. La gestione dei campi di data e ora non è certamente scienza missilistica, ma spesso può essere noiosa e soggetta a errori.
Ci sono centinaia di articoli sull'argomento là fuori, tuttavia, la maggior parte o sono troppo accademici, concentrandosi su dettagli nitidi, o sono troppo irregolari, fornendo brevi frammenti di codice senza molte spiegazioni che li accompagnano. Questa guida approfondita alla manipolazione di DateTime dovrebbe aiutarti a comprendere i concetti di programmazione e le migliori pratiche relative all'ora e alla data senza dover sfogliare un mare di informazioni su questo argomento.
In questo articolo, ti aiuterò a pensare chiaramente ai campi di data e ora e suggerirò alcune best practice che possono aiutarti a evitare l'inferno di data e ora. Qui esploreremo alcuni dei concetti chiave necessari per manipolare correttamente i valori di data e ora, formati utili per archiviare valori DateTime e trasferirli tramite API e altro ancora.
Tanto per cominciare, la risposta giusta per il codice di produzione è quasi sempre quella di utilizzare una libreria adeguata anziché utilizzarne una propria. Le potenziali difficoltà con il calcolo DateTime discusse in questo articolo sono solo la punta dell'iceberg, ma sono comunque utili da conoscere, con o senza una libreria.
Le librerie DateTime aiutano se le capisci correttamente
Le librerie di date aiutano in molti modi a semplificarti la vita. Semplificano notevolmente l'analisi della data, le operazioni aritmetiche e logiche della data e la formattazione della data. Puoi trovare una libreria di date affidabile sia per il front-end che per il back-end per fare la maggior parte del lavoro pesante per te.
Tuttavia, utilizziamo spesso le librerie di date senza pensare a come funzionano effettivamente data/ora. Data/ora è un concetto complesso. I bug che emergono a causa della sua errata comprensione possono essere estremamente difficili da capire e correggere, anche con l'aiuto delle librerie di date. Come programmatore, devi comprendere le basi ed essere in grado di apprezzare i problemi che le librerie di date risolvono per trarne il massimo.
Inoltre, le librerie di data/ora possono portarti solo così lontano. Tutte le librerie di date funzionano dandoti accesso a comode strutture di dati per rappresentare un DateTime. Se stai inviando e ricevendo dati tramite un'API REST, alla fine dovrai convertire la data in una stringa e viceversa perché JSON non ha una struttura dati nativa per rappresentare DateTime. I concetti che ho delineato qui ti aiuteranno a evitare alcuni dei problemi comuni che potrebbero sorgere quando esegui queste trasformazioni da data a stringa e da stringa a data.
Nota: anche se ho usato JavaScript come linguaggio di programmazione discusso in questo articolo, questi sono concetti generali che si applicano, in larga misura, a praticamente tutti i linguaggi di programmazione e alle relative librerie di date. Quindi, anche se non hai mai scritto una riga di JavaScript prima, sentiti libero di continuare a leggere poiché non presumo quasi nessuna conoscenza precedente di JavaScript nell'articolo.
Standardizzare il tempo
Un DateTime è un momento molto specifico. Pensiamo a questo. Mentre scarabocchio questo articolo, l'orologio sul mio laptop indica il 21 luglio alle 13:29. Questo è ciò che chiamiamo "ora locale", l'ora che vedo sugli orologi da parete intorno a me e sul mio orologio da polso.
Concediti o prenditi qualche minuto, se chiedo alla mia amica di incontrarmi in un bar vicino alle 15:00, posso aspettarmi di vederla lì all'incirca a quell'ora. Allo stesso modo, non ci sarebbe confusione se invece dicessi, ad esempio, “incontriamoci tra un'ora e mezza”. Parliamo abitualmente dell'ora in questo modo con persone che vivono nella stessa città o fuso orario.
Pensiamo a uno scenario diverso: voglio dire a un amico che vive a Uppsala, in Svezia, che voglio parlargli alle 17:00. Gli mando un messaggio: "Ehi Anton, parliamo alle 17:00". Ottengo immediatamente la risposta: "Il tuo tempo o il mio tempo?"
Anton mi dice che vive nel fuso orario dell'Europa centrale che è UTC+01:00. Vivo in UTC+05:45. Ciò significa che quando sono le 17:00 dove vivo, sono le 17:00 - 05:45 = 11:15 UTC, che si traduce in 11:15 UTC + 01:00 = 12:15 a Uppsala, perfetto per entrambi di noi.
Inoltre, tieni presente la differenza tra il fuso orario (ora dell'Europa centrale) e l'offset del fuso orario (UTC+05:45). I paesi possono decidere di modificare la differenza di fuso orario per l'ora legale anche per motivi politici. Quasi ogni anno c'è una modifica alle regole in almeno un paese, il che significa che qualsiasi codice con queste regole integrate deve essere mantenuto aggiornato: vale la pena considerare da cosa dipende la tua base di codice per ogni livello della tua app.
Questo è un altro buon motivo per cui consigliamo che solo il front-end gestisca i fusi orari nella maggior parte dei casi. In caso contrario, cosa succede quando le regole utilizzate dal motore di database non corrispondono a quelle del front-end o del back-end?
Questo problema di gestire due diverse versioni del tempo, relative all'utente e relative ad uno standard universalmente accettato, è difficile, a maggior ragione nel mondo della programmazione dove la precisione è fondamentale e anche un solo secondo può fare una grande differenza. Il primo passo per risolvere questi problemi è archiviare DateTime in UTC.
Standardizzazione del formato
Standardizzare l'ora è meraviglioso perché ho solo bisogno di memorizzare l'ora UTC e fintanto che conosco il fuso orario dell'utente, posso sempre convertire la sua ora. Al contrario, se conosco l'ora locale di un utente e conosco il suo fuso orario, posso convertirla in UTC.
Ma le date e gli orari possono essere specificati in molti formati diversi. Per la data, potresti scrivere "30 luglio" o "30 luglio" o "30/7" (o 30/7, a seconda di dove vivi). Per il momento, potresti scrivere "21:30" o "2130".
Scienziati di tutto il mondo si sono riuniti per affrontare questo problema e hanno deciso un formato per descrivere il tempo che piace molto ai programmatori perché è breve e preciso. Ci piace chiamarlo "formato data ISO", che è una versione semplificata del formato esteso ISO-8601 e si presenta così:
Per 00:00 o UTC, utilizziamo invece "Z", che significa ora Zulu, un altro nome per UTC.
Manipolazione della data e aritmetica in JavaScript
Prima di iniziare con le migliori pratiche, impareremo la manipolazione della data utilizzando JavaScript per comprendere la sintassi e i concetti generali. Sebbene utilizziamo JavaScript, puoi adattare facilmente queste informazioni al tuo linguaggio di programmazione preferito.
Useremo l'aritmetica della data per risolvere i problemi comuni relativi alla data che la maggior parte degli sviluppatori incontra.
Il mio obiettivo è farti sentire a tuo agio nel creare un oggetto data da una stringa ed estrarre i componenti da uno. Questo è qualcosa con cui una libreria di date può aiutarti, ma è sempre meglio capire come viene fatto dietro le quinte.
Una volta che ci siamo sporcati le mani con data/ora, è più facile pensare ai problemi che dobbiamo affrontare, estrarre le migliori pratiche e andare avanti. Se vuoi saltare alle migliori pratiche, sentiti libero di farlo, ma ti consiglio vivamente di sfogliare almeno la sezione aritmetica della data di seguito.
L'oggetto data JavaScript
I linguaggi di programmazione contengono costrutti utili per semplificarci la vita. L'oggetto JavaScript Date
è una di queste cose. Offre metodi convenienti per ottenere la data e l'ora correnti, memorizzare una data in una variabile, eseguire aritmetica della data e formattare la data in base alle impostazioni locali dell'utente.
A causa delle differenze tra le implementazioni del browser e la gestione errata dell'ora legale (DST), la dipendenza dall'oggetto Date per le applicazioni mission-critical non è consigliata e probabilmente dovresti usare una libreria DateTime come Luxon, date-fns o dayjs. (Qualunque cosa tu usi, evita il popolare Moment.js, spesso chiamato semplicemente moment
, come appare nel codice, poiché ora è deprecato.)
Ma per scopi didattici, useremo i metodi forniti dall'oggetto Date() per imparare come JavaScript gestisce DateTime.
Ottenere la data corrente
const currentDate = new Date();
Se non si passa nulla al costruttore Date, l'oggetto date restituito contiene la data e l'ora correnti.
Puoi quindi formattarlo per estrarre solo la parte della data come segue:
const currentDate = new Date(); const currentDayOfMonth = currentDate.getDate(); const currentMonth = currentDate.getMonth(); // Be careful! January is 0, not 1 const currentYear = currentDate.getFullYear(); const dateString = currentDayOfMonth + "-" + (currentMonth + 1) + "-" + currentYear; // "27-11-2020"
Nota: l'insidia "gennaio è 0" è comune ma non universale. Vale la pena ricontrollare la documentazione di qualsiasi lingua (o formato di configurazione: ad esempio, cron è in particolare basato su 1) prima di iniziare a usarlo.
Ottenere il timestamp corrente
Se invece vuoi ottenere il timestamp corrente, puoi creare un nuovo oggetto Date e usare il metodo getTime().
const currentDate = new Date(); const timestamp = currentDate.getTime();
In JavaScript, un timestamp è il numero di millisecondi trascorsi dal 1 gennaio 1970.
Se non intendi supportare <IE8, puoi utilizzare Date.now()
per ottenere direttamente il timestamp senza dover creare un nuovo oggetto Date.
Analisi di una data
La conversione di una stringa in un oggetto data JavaScript viene eseguita in diversi modi.
Il costruttore dell'oggetto Date accetta un'ampia varietà di formati di data:
const date1 = new Date("Wed, 27 July 2016 13:30:00"); const date2 = new Date("Wed, 27 July 2016 07:45:00 UTC"); const date3 = new Date("27 July 2016 13:30:00 UTC+05:45");
Nota che non è necessario includere il giorno della settimana perché JS può determinare il giorno della settimana per qualsiasi data.
Puoi anche passare anno, mese, giorno, ore, minuti e secondi come argomenti separati:
const date = new Date(2016, 6, 27, 13, 30, 0);
Naturalmente, puoi sempre utilizzare il formato della data ISO:
const date = new Date("2016-07-27T07:45:00Z");
Tuttavia, potresti avere problemi quando non fornisci esplicitamente il fuso orario!
const date1 = new Date("25 July 2016"); const date2 = new Date("July 25, 2016");
Ognuno di questi ti darà il 25 luglio 2016 00:00:00 ora locale.
Se utilizzi il formato ISO, anche se fornisci solo la data e non l'ora e il fuso orario, accetterà automaticamente il fuso orario come UTC.
Ciò significa che:
new Date("25 July 2016").getTime() !== new Date("2016-07-25").getTime() new Date("2016-07-25").getTime() === new Date("2016-07-25T00:00:00Z").getTime()
Formattazione di una data
Fortunatamente, il moderno JavaScript ha alcune comode funzioni di internazionalizzazione integrate nello spazio dei nomi Intl
standard che rendono la formattazione della data un'operazione semplice.
Per questo avremo bisogno di due oggetti: un Date
e un Intl.DateTimeFormat
, inizializzati con le nostre preferenze di output. Supponendo di voler utilizzare il formato americano (M/G/AAAA), questo sarebbe simile a:
const firstValentineOfTheDecade = new Date(2020, 1, 14); // 1 for February const enUSFormatter = new Intl.DateTimeFormat('en-US'); console.log(enUSFormatter.format(firstValentineOfTheDecade)); // 2/14/2020
Se invece volessimo il formato olandese (G/M/AAAA), passeremmo semplicemente un codice cultura diverso al costruttore DateTimeFormat
:
const nlBEFormatter = new Intl.DateTimeFormat('nl-BE'); console.log(nlBEFormatter.format(firstValentineOfTheDecade)); // 14/2/2020
O una forma più lunga del formato americano, con il nome del mese esplicitato:
const longEnUSFormatter = new Intl.DateTimeFormat('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); console.log(longEnUSFormatter.format(firstValentineOfTheDecade)); // February 14, 2020
Ora, se volessimo un formato ordinale corretto nel giorno del mese, cioè "14°" invece di solo "14", questo purtroppo ha bisogno di una soluzione alternativa, perché gli unici valori validi del day
al momento della stesura di questo articolo sono "numeric"
o "2-digit"
. Prendendo in prestito la versione di Flavio Copes del codice di Mathias Bynens per sfruttare un'altra parte di Intl
per questo, possiamo personalizzare l'output del giorno del mese tramite formatToParts()
:
const pluralRules = new Intl.PluralRules('en-US', { type: 'ordinal' }) const suffixes = { 'one': 'st', 'two': 'nd', 'few': 'rd', 'other': 'th' } const convertToOrdinal = (number) => `${number}${suffixes[pluralRules.select(number)]}` // At this point: // convertToOrdinal("1") === "1st" // convertToOrdinal("2") === "2nd" // etc. const extractValueAndCustomizeDayOfMonth = (part) => { if (part.type === "day") { return convertToOrdinal(part.value); } return part.value; }; console.log( longEnUSFormatter.formatToParts(firstValentineOfTheDecade) .map(extractValueAndCustomizeDayOfMonth) .join("") ); // February 14th, 2020
Sfortunatamente, formatToParts
non è affatto supportato da Internet Explorer (IE) al momento della stesura di questo documento, ma tutte le altre tecnologie desktop, mobili e back-end (ad es. Node.js) hanno il supporto. Per coloro che hanno bisogno di supportare IE e hanno assolutamente bisogno degli ordinali, la nota a margine di seguito (o meglio, una libreria di date adeguata) fornisce una risposta.
Se è necessario supportare browser più vecchi come IE prima della versione 11, la formattazione della data in JavaScript è più difficile perché non esistevano funzioni standard di formattazione della data come strftime
in Python o PHP.
In PHP, ad esempio, la funzione strftime("Today is %b %d %Y %X", mktime(5,10,0,12,30,99))
ti dà Today is Dec 30 1999 05:10:00
.
Puoi utilizzare una diversa combinazione di lettere preceduta da %
per ottenere la data in diversi formati. (Attenzione, non tutte le lingue assegnano lo stesso significato a ciascuna lettera, in particolare, "M" e "m" possono essere scambiate per minuti e mesi.)
Se sei sicuro del formato che desideri utilizzare, è meglio estrarre i singoli bit utilizzando le funzioni JavaScript di cui abbiamo parlato sopra e creare tu stesso una stringa.
var currentDate = new Date (); var date = currentDate.getDate(); var month = currentDate.getMonth(); var year = currentDate.getFullYear();
Possiamo ottenere la data nel formato MM/GG/AAAA come
var monthDateYear = (month+ 1 ) + "/" + date + "/" + year;
Il problema con questa soluzione è che può dare una lunghezza incoerente alle date perché alcuni mesi e giorni del mese sono a una cifra e altri a due cifre. Questo può essere problematico, ad esempio, se stai visualizzando la data in una colonna di tabella, perché le date non sono allineate.
Possiamo risolvere questo problema usando una funzione "pad" che aggiunge uno 0 iniziale.
function pad ( n ) { return n< 10 ? '0' +n : n; }
Ora, otteniamo la data corretta nel formato MM/GG/AAAA utilizzando:
var mmddyyyy = pad(month + 1 ) + "/" + pad(date) + "/" + year;
Se invece vogliamo GG-MM-AAAA, il processo è simile:
var ddmmyyyy = pad(date) + "-" + pad(month + 1 ) + "-" + year;
Alziamo la posta e proviamo a stampare la data nel formato "Mese Data, Anno". Avremo bisogno di una mappatura degli indici dei mesi ai nomi:
var monthNames = [ "January" , "February" , "March" , "April" , "May" , "June" , "July" , "August" , "September" , "October" , "November" , "December" ]; var dateWithFullMonthName = monthNames[month] + " " + pad(date) + ", " + year;
Ad alcune persone piace visualizzare la data come 1° gennaio 2013. Nessun problema, tutto ciò di cui abbiamo bisogno è una funzione di supporto ordinal
che restituisca 1° per 1, 12° per 12 e 103° per 103, ecc., e il resto è semplice:
var ordinalDate = ordinal(date) + " " + monthNames[month] + ", " + year;
È facile determinare il giorno della settimana dall'oggetto data, quindi aggiungiamolo in:
var daysOfWeek = [ "Sun" , "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" ]; ordinalDateWithDayOfWeek = daysOfWeek[currentDate.getDay()] + ", " + ordinalDate;
Il punto più importante qui è che una volta estratti i numeri dalla data, la formattazione è principalmente correlata alle stringhe.
Modifica del formato della data
Una volta che sai come analizzare una data e formattarla, cambiare una data da un formato all'altro è solo questione di combinare i due.
Ad esempio, se si dispone di una data nel formato 21 luglio 2013 e si desidera modificare il formato in 21-07-2013, è possibile ottenerla in questo modo:
const myDate = new Date("Jul 21, 2013"); const dayOfMonth = myDate.getDate(); const month = myDate.getMonth(); const year = myDate.getFullYear(); function pad(n) { return n<10 ? '0'+n : n } const ddmmyyyy = pad(dayOfMonth) + "-" + pad(month + 1) + "-" + year; // "21-07-2013"
Utilizzo delle funzioni di localizzazione di JavaScript Date Object
I metodi di formattazione della data di cui abbiamo discusso sopra dovrebbero funzionare nella maggior parte delle applicazioni, ma se vuoi davvero localizzare la formattazione della data, ti suggerisco di utilizzare il metodo toLocaleDateString()
dell'oggetto Date
:

const today = new Date().toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: 'numeric', });
… ci dà qualcosa come il 26 Jul 2016
.
La modifica della locale in "en-US" restituisce invece "26 luglio 2016". Nota come è cambiata la formattazione, ma le opzioni di visualizzazione sono rimaste le stesse, una funzionalità molto utile. Come mostrato nella sezione precedente, la tecnica più recente basata su Intl.DateTimeFormat
funziona in modo molto simile a questa, ma consente di riutilizzare un oggetto formattatore in modo che sia necessario impostare le opzioni solo una volta.
Con toLocaleDateString()
, è buona abitudine passare sempre le opzioni di formattazione, anche se l'output sembra corretto sul tuo computer. Questo può proteggere l'interfaccia utente dall'irruzione in locali imprevisti con nomi di mesi molto lunghi o dall'aspetto imbarazzante a causa di quelli brevi.
Se invece volessi il mese intero "luglio", tutto ciò che faccio è modificare il parametro del mese nelle opzioni su "lungo". JavaScript gestisce tutto per me. Per en-US, ora ricevo il 26 luglio 2016.
Nota: se si desidera che il browser utilizzi automaticamente le impostazioni locali dell'utente, è possibile passare "non definito" come primo parametro.
Se vuoi mostrare la versione numerica della data e non vuoi preoccuparti di MM/GG/AAAA rispetto a GG/MM/AAAA per diverse impostazioni locali, ti suggerisco la seguente semplice soluzione:
const today = new Date().toLocaleDateString(undefined, { day: 'numeric', month: 'numeric', year: 'numeric', });
Sul mio computer, questo 7/26/2016
. Se vuoi assicurarti che il mese e la data abbiano due cifre, cambia semplicemente le opzioni:
const today = new Date().toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric', });
Questo 07/26/2016
. Proprio quello che volevamo!
Puoi anche utilizzare alcune altre funzioni correlate per localizzare il modo in cui vengono visualizzate sia l'ora che la data:
Codice | Produzione | Descrizione |
---|---|---|
| "4:21:38 AM" | Visualizza la versione localizzata della sola ora |
| "04:21:38" | Visualizza l'ora localizzata in base alle opzioni fornite |
| "22/07/2016, 4:21:38" | Visualizza la data e l'ora per le impostazioni locali dell'utente |
| "22/07/2016, 04:21" | Visualizza data e ora localizzate in base alle opzioni fornite |
Calcolo di date e orari relativi
Ecco un esempio di aggiunta di 20 giorni a una data JavaScript (ovvero, calcolare la data 20 giorni dopo una data nota):
const myDate = new Date("July 20, 2016 15:00:00"); const nextDayOfMonth = myDate.getDate() + 20; myDate.setDate(nextDayOfMonth); const newDate = myDate.toLocaleString();
L'oggetto data originale ora rappresenta una data 20 giorni dopo il 20 luglio e newDate
contiene una stringa localizzata che rappresenta quella data. Sul mio browser, newDate
contiene "09/08/2016, 15:00:00".
Per calcolare i timestamp relativi con una differenza più precisa rispetto ai giorni interi, puoi utilizzare Date.getTime()
e Date.setTime()
per lavorare con numeri interi che rappresentano il numero di millisecondi da una certa epoca, ovvero il 1 gennaio 1970. Per esempio, se vuoi sapere quando sono trascorse 17 ore in questo momento:
const msSinceEpoch = (new Date()).getTime(); const seventeenHoursLater = new Date(msSinceEpoch + 17 * 60 * 60 * 1000);
Date a confronto
Come per tutto il resto relativo alla data, il confronto delle date ha i suoi trucchi.
Innanzitutto, dobbiamo creare oggetti data. Fortunatamente, <, >, <= e >= funzionano tutti. Quindi confrontare il 19 luglio 2014 e il 18 luglio 2014 è facile come:
const date1 = new Date("July 19, 2014"); const date2 = new Date("July 28, 2014"); if(date1 > date2) { console.log("First date is more recent"); } else { console.log("Second date is more recent"); }
Il controllo dell'uguaglianza è più complicato, poiché due oggetti data che rappresentano la stessa data sono ancora due oggetti data diversi e non saranno uguali. Confrontare le stringhe di date è una cattiva idea perché, ad esempio, "20 luglio 2014" e "20 luglio 2014" rappresentano la stessa data ma hanno rappresentazioni di stringhe diverse. Lo snippet seguente illustra il primo punto:
const date1 = new Date("June 10, 2003"); const date2 = new Date(date1); const equalOrNot = date1 == date2 ? "equal" : "not equal"; console.log(equalOrNot);
Questo produrrà not equal
.
Questo caso particolare può essere risolto confrontando gli equivalenti interi delle date (i loro timestamp) come segue:
date1.getTime() == date2.getTime()
Ho visto questo esempio in molti posti, ma non mi piace perché di solito non crei un oggetto data da un altro oggetto data. Quindi ritengo che l'esempio sia importante solo da un punto di vista accademico. Inoltre, ciò richiede che entrambi gli oggetti Date si riferiscano esattamente allo stesso secondo, mentre potresti voler solo sapere se si riferiscono allo stesso giorno o ora o minuto.
Diamo un'occhiata a un esempio più pratico. Stai cercando di confrontare se la data di nascita inserita dall'utente è la stessa della data fortunata che stai ricevendo da un'API.
const userEnteredString = "12/20/1989"; // MM/DD/YYYY format const dateStringFromAPI = "1989-12-20T00:00:00Z"; const dateFromUserEnteredString = new Date(userEnteredString) const dateFromAPIString = new Date(dateStringFromAPI); if (dateFromUserEnteredString.getTime() == dateFromAPIString.getTime()) { transferOneMillionDollarsToUserAccount(); } else { doNothing(); }
Entrambi rappresentavano la stessa data, ma sfortunatamente il tuo utente non riceverà il milione di dollari.
Ecco il problema: JavaScript presuppone sempre che il fuso orario sia quello fornito dal browser se non diversamente specificato.
Ciò significa, per me, la new Date ("12/20/1989")
creerà una data 1989-12-20T00:00:00+5:45 o 1989-12-19T18:15:00Z che non è la stessa di 1989-12-20T00:00:00Z in termini di timestamp.
Non è possibile modificare solo il fuso orario di un oggetto data esistente, quindi il nostro obiettivo ora è creare un nuovo oggetto data ma con UTC invece del fuso orario locale.
Ignoreremo il fuso orario dell'utente e utilizzeremo UTC durante la creazione dell'oggetto data. Ci sono due modi per farlo:
- Crea una stringa di data formattata ISO dalla data di input dell'utente e usala per creare un oggetto Date. Utilizzo di un formato di data ISO valido per creare un oggetto Date rendendo molto chiaro l'intento di UTC rispetto a locale.
const userEnteredDate = "12/20/1989"; const parts = userEnteredDate.split("/"); const userEnteredDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const userEnteredDateObj = new Date(userEnteredDateISO + "T00:00:00Z"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateObj.getTime() == dateFromAPI.getTime(); // true
Funziona anche se non specifichi l'ora poiché sarà predefinita a mezzanotte (ad esempio, 00:00:00Z):
const userEnteredDate = new Date("1989-12-20"); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDate.getTime() == dateFromAPI.getTime(); // true
Ricorda: se al costruttore della data viene passata una stringa nel formato di data ISO corretto di AAAA-MM-GG, presuppone automaticamente UTC.
- JavaScript fornisce un'accurata funzione Date.UTC() che puoi utilizzare per ottenere il timestamp UTC di una data. Estraiamo i componenti dalla data e li passiamo alla funzione.
const userEnteredDate = new Date("12/20/1989"); const userEnteredDateTimeStamp = Date.UTC(userEnteredDate.getFullYear(), userEnteredDate.getMonth(), userEnteredDate.getDate(), 0, 0, 0); const dateFromAPI = new Date("1989-12-20T00:00:00Z"); const result = userEnteredDateTimeStamp == dateFromAPI.getTime(); // true ...
Trovare la differenza tra due date
Uno scenario comune che incontrerai è trovare la differenza tra due date.
Discutiamo di due casi d'uso:
Trovare il numero di giorni tra due date
Converti entrambe le date in timestamp UTC, trova la differenza in millisecondi e trova i giorni equivalenti.
const dateFromAPI = "2016-02-10T00:00:00Z"; const now = new Date(); const datefromAPITimeStamp = (new Date(dateFromAPI)).getTime(); const nowTimeStamp = now.getTime(); const microSecondsDiff = Math.abs(datefromAPITimeStamp - nowTimeStamp); // Math.round is used instead of Math.floor to account for certain DST cases // Number of milliseconds per day = // 24 hrs/day * 60 minutes/hour * 60 seconds/minute * 1000 ms/second const daysDiff = Math.round(microSecondsDiff / (1000 * 60 * 60 * 24)); console.log(daysDiff);
Trovare l'età dell'utente dalla data di nascita
const birthDateFromAPI = "12/10/1989";
Nota: abbiamo un formato non standard. Leggi il documento API per determinare se questo significa 12 ottobre o 10 dicembre. Passa al formato ISO di conseguenza.
const parts = birthDateFromAPI.split("/"); const birthDateISO = parts[2] + "-" + parts[0] + "-" + parts[1]; const birthDate = new Date(birthDateISO); const today = new Date(); let age = today.getFullYear() - birthDate.getFullYear(); if(today.getMonth() < birthDate.getMonth()) { age--; } if(today.getMonth() == birthDate.getMonth() && today.getDate() < birthDate.getDate()) { age--; }
So che ci sono modi più concisi per scrivere questo codice, ma mi piace scriverlo in questo modo a causa della pura chiarezza della logica.
Suggerimenti per evitare Date Hell
Ora che siamo a nostro agio con l'aritmetica della data, siamo in grado di comprendere le migliori pratiche da seguire e le ragioni per seguirle.
Ottenere DateTime dall'utente
Se stai ricevendo la data e l'ora dall'utente, molto probabilmente stai cercando il loro DateTime locale. Abbiamo visto nella sezione aritmetica della data che il costruttore Date
può accettare la data in diversi modi.
Per eliminare qualsiasi confusione, suggerisco sempre di creare una data utilizzando il new Date(year, month, day, hours, minutes, seconds, milliseconds)
anche se hai già la data in un formato analizzabile valido. Se tutti i programmatori del tuo team seguono questa semplice regola, sarà estremamente facile mantenere il codice a lungo termine poiché è il più esplicito possibile con il costruttore Date
.
La parte interessante è che puoi utilizzare le variazioni che ti consentono di omettere uno qualsiasi degli ultimi quattro parametri se sono zero; ovvero, new Date(2012, 10, 12)
è uguale a new Date(2012, 10, 12, 0, 0, 0, 0)
perché i parametri non specificati sono impostati su zero.
Ad esempio, se si utilizza un selettore di data e ora che fornisce la data 12-10-2012 e l'ora 12:30, è possibile estrarre le parti e creare un nuovo oggetto Data come segue:
const dateFromPicker = "2012-10-12"; const timeFromPicker = "12:30"; const dateParts = dateFromPicker.split("-"); const timeParts = timeFromPicker.split(":"); const localDate = new Date(dateParts[0], dateParts[1]-1, dateParts[2], timeParts[0], timeParts[1]);
Cerca di evitare di creare una data da una stringa a meno che non sia in formato data ISO. Utilizzare invece il metodo Data(anno, mese, data, ore, minuti, secondi, microsecondi).
Ottenere solo la data
Se stai ricevendo solo la data, ad esempio la data di nascita di un utente, è meglio convertire il formato in un formato di data ISO valido per eliminare qualsiasi informazione sul fuso orario che può causare lo spostamento della data in avanti o indietro quando viene convertita in UTC. Per esempio:
const dateFromPicker = "12/20/2012"; const dateParts = dateFromPicker.split("/"); const ISODate = dateParts[2] + "-" + dateParts[0] + "-" + dateParts[1]; const birthDate = new Date(ISODate).toISOString();
Nel caso in cui l'hai dimenticato, se crei un oggetto Date
con l'input in un formato di data ISO valido (AAAA-MM-GG), verrà impostato automaticamente su UTC anziché sul fuso orario del browser.
Memorizzazione della data
Memorizzare sempre DateTime in UTC. Invia sempre una stringa di data ISO o un timestamp al back-end.
Generazioni di programmatori di computer hanno realizzato questa semplice verità dopo amare esperienze cercando di mostrare all'utente l'ora locale corretta. La memorizzazione dell'ora locale nel back-end è una cattiva idea, è meglio lasciare che il browser gestisca la conversione all'ora locale nel front-end.
Inoltre, dovrebbe essere evidente che non dovresti mai inviare una stringa DateTime come "20 luglio 1989 12:10" al back-end. Anche se invii anche il fuso orario, aumenti lo sforzo degli altri programmatori per comprendere le tue intenzioni e analizzare e memorizzare correttamente la data.
Utilizzare i toISOString()
o toJSON()
dell'oggetto Date per convertire il DateTime locale in UTC.
const dateFromUI = "12-13-2012"; const timeFromUI = "10:20"; const dateParts = dateFromUI.split("-"); const timeParts = timeFromUI.split(":"); const date = new Date(dateParts[2], dateParts[0]-1, dateParts[1], timeParts[0], timeParts[1]); const dateISO = date.toISOString(); $.post("http://example.com/", {date: dateISO}, ...)
Visualizzazione della data e dell'ora
- Ottieni il timestamp o la data formattata ISO da un'API REST.
- Crea un oggetto
Date
. - Utilizzare i
toLocaleString()
otoLocaleDateString()
etoLocaleTimeString()
o una libreria di date per visualizzare l'ora locale.
const dateFromAPI = "2016-01-02T12:30:00Z"; const localDate = new Date(dateFromAPI); const localDateString = localDate.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric', }); const localTimeString = localDate.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', });
Quando dovresti memorizzare anche l'ora locale?
“A volte è importante conoscere il fuso orario in cui si è verificato un evento e la conversione in un unico fuso orario cancella irrevocabilmente tali informazioni.
"Se stai facendo una promozione di marketing e vuoi sapere quali clienti hanno effettuato ordini intorno all'ora di pranzo, un ordine che sembra essere stato effettuato a mezzogiorno GMT non è molto utile quando è stato effettivamente effettuato a colazione a New York."
Se ti imbatti in questo tipo di situazione, sarebbe più saggio salvare anche l'ora locale. Come al solito, vorremmo creare la data in formato ISO, ma prima dobbiamo trovare l'offset del fuso orario.
La funzione getTimeZoneOffset()
dell'oggetto Date ci dice il numero di minuti che, sommati a una data ora locale, danno l'equivalente ora UTC. Suggerisco di convertirlo nel formato (+-)hh:mm perché rende più ovvio che si tratta di un offset di fuso orario.
const now = new Date(); const tz = now.gettime zoneOffset();
Per il mio fuso orario +05:45, ottengo -345, questo non è solo il segno opposto, ma un numero come -345 potrebbe lasciare perplessi uno sviluppatore back-end. Quindi lo convertiamo in +05:45.
const sign = tz > 0 ? "-" : "+"; const hours = pad(Math.floor(Math.abs(tz)/60)); const minutes = pad(Math.abs(tz)%60); const tzOffset = sign + hours + ":" + minutes;
Ora otteniamo il resto dei valori e creiamo una stringa ISO valida che rappresenta il DateTime locale.
const localDateTime = now.getFullYear() + "-" + pad(now.getMonth()+1) + "-" + pad(now.getDate()) + "T" + pad(now.getHours()) + ":" + pad(now.getMinutes()) + ":" + pad(now.getSeconds());
Se lo desideri, puoi racchiudere le date UTC e locali in un oggetto.
const eventDate = { utc: now.toISOString(), local: localDateTime, tzOffset: tzOffset, }
Ora, nel back-end, se vuoi scoprire se l'evento si è verificato prima di mezzogiorno, ora locale, puoi analizzare la data e usare semplicemente la funzione getHours()
.
const localDateString = eventDate.local; const localDate = new Date(localDateString); if(localDate.getHours() < 12) { console.log("Event happened before noon local time"); }
Non abbiamo usato tzOffset
qui, ma lo memorizziamo comunque perché potremmo averne bisogno in futuro per scopi di debug. In realtà potresti semplicemente inviare l'offset del fuso orario e solo l'ora UTC. Ma mi piace anche memorizzare l'ora locale perché alla fine dovrai memorizzare la data in un database e avere l'ora locale memorizzata separatamente ti consente di interrogare direttamente in base a un campo piuttosto che dover eseguire calcoli per ottenere la data locale.
A volte, anche con il fuso orario locale memorizzato, ti consigliamo di visualizzare le date in un particolare fuso orario. Ad esempio, gli orari degli eventi potrebbero avere più senso nel fuso orario dell'utente corrente se sono virtuali o nel fuso orario in cui si svolgeranno fisicamente, se non lo sono. In ogni caso, vale la pena esaminare in anticipo le soluzioni consolidate per la formattazione con nomi di fuso orario espliciti.
Configurazione server e database
Configura sempre i tuoi server e database per utilizzare il fuso orario UTC. (Si noti che UTC e GMT non sono la stessa cosa: GMT, ad esempio, potrebbe implicare un passaggio all'ora legale durante l'estate, mentre UTC non lo farà mai.)
Abbiamo già visto quanto possono essere dolorose le conversioni del fuso orario, soprattutto quando non sono intenzionali. L'invio sempre di DateTime UTC e la configurazione dei server in modo che siano nel fuso orario UTC possono semplificarti la vita. Il tuo codice di back-end sarà molto più semplice e pulito in quanto non deve eseguire conversioni di fuso orario. DateTime data coming in from servers across the world can be compared and sorted effortlessly.
Code in the back end should be able to assume the time zone of the server to be UTC (but should still have a check in place to be sure). A simple configuration check saves having to think about and code for conversions every time new DateTime code is written.
It's Time for Better Date Handling
Date manipulation is a hard problem. The concepts behind the practical examples in this article apply beyond JavaScript, and are just the beginning when it comes to properly handling DateTime data and calculations. Plus, every helper library will come with its own set of nuances—which is even true of the eventual official standard support{target=”_blank”} for these types of operations.
The bottom line is: Use ISO on the back end, and leave the front end to format things properly for the user. Professional programmers will be aware of some of the nuances, and will (all the more decidedly) use well-supported DateTime libraries on both the back end and the front end. Built-in functions on the database side are another story, but hopefully this article gives enough background to make wiser decisions in that context, too.