Durante la recente esperienza nella scrittura di un interprete di JS ho lottato molto con il funzionamento interno delle date ECMA / JS. Quindi, immagino che lancerò qui i miei 2 centesimi. Spero che condividere queste cose possa aiutare gli altri con qualsiasi domanda sulle differenze tra i browser nel modo in cui gestiscono le date.
Il lato input
Tutte le implementazioni memorizzano i loro valori di data internamente come numeri a 64 bit che rappresentano il numero di millisecondi (ms) dal 1970-01-01 UTC (GMT è la stessa cosa di UTC). Questa data è l'epoca di ECMAScript utilizzata anche da altri linguaggi come i sistemi Java e POSIX come UNIX. Le date che si verificano dopo l'epoca sono numeri positivi e le date precedenti sono negative.
Il codice seguente viene interpretato come la stessa data in tutti i browser correnti, ma con l'offset del fuso orario locale:
Date.parse('1/1/1970'); // 1 January, 1970
Nel mio fuso orario (EST, che è -05: 00), il risultato è 18000000 perché sono quanti ms sono in 5 ore (sono solo 4 ore durante i mesi di ora legale). Il valore sarà diverso in diversi fusi orari. Questo comportamento è specificato in ECMA-262, quindi tutti i browser lo fanno allo stesso modo.
Sebbene vi sia una certa varianza nei formati delle stringhe di input che i principali browser analizzeranno come date, essenzialmente li interpretano allo stesso modo per quanto riguarda i fusi orari e l'ora legale anche se l'analisi dipende in larga misura dall'implementazione.
Tuttavia, il formato ISO 8601 è diverso. È uno dei due soli formati delineati in ECMAScript 2015 (n. 6) specificamente che deve essere analizzato allo stesso modo da tutte le implementazioni (l'altro è il formato specificato per Date.prototype.toString ).
Ma, anche per le stringhe in formato ISO 8601, alcune implementazioni sbagliano. Ecco un output di confronto di Chrome e Firefox quando questa risposta è stata originariamente scritta per 1/1/1970 (l'epoca) sulla mia macchina usando stringhe in formato ISO 8601 che dovrebbero essere analizzate esattamente allo stesso valore in tutte le implementazioni:
Date.parse('1970-01-01T00:00:00Z'); // Chrome: 0 FF: 0
Date.parse('1970-01-01T00:00:00-0500'); // Chrome: 18000000 FF: 18000000
Date.parse('1970-01-01T00:00:00'); // Chrome: 0 FF: 18000000
- Nel primo caso, l'identificatore "Z" indica che l'ingresso è in ora UTC, quindi non è sfalsato rispetto all'epoca e il risultato è 0
- Nel secondo caso, l'identificatore "-0500" indica che l'input è in GMT-05: 00 ed entrambi i browser interpretano l'input come nel fuso orario -05: 00. Ciò significa che il valore UTC è sfalsato rispetto all'epoca, il che significa aggiungere 18000000ms al valore di ora interno della data.
- Il terzo caso, in cui non è presente alcun identificatore, deve essere trattato come locale per il sistema host. FF tratta correttamente l'input come ora locale mentre Chrome lo considera come UTC, producendo quindi valori orari diversi. Per me questo crea una differenza di 5 ore nel valore memorizzato, il che è problematico. Altri sistemi con offset diversi otterranno risultati diversi.
Questa differenza è stata risolta a partire dal 2020, ma esistono altre stranezze tra i browser durante l'analisi delle stringhe in formato ISO 8601.
Ma peggiora. Una stranezza di ECMA-262 è che il formato di sola data ISO 8601 (AAAA-MM-GG) deve essere analizzato come UTC, mentre ISO 8601 richiede che sia analizzato come locale. Ecco l'output di FF con i formati di data ISO lunghi e brevi senza specificatore di fuso orario.
Date.parse('1970-01-01T00:00:00'); // 18000000
Date.parse('1970-01-01'); // 0
Quindi il primo viene analizzato come locale perché è la data e l'ora ISO 8601 senza fuso orario e il secondo viene analizzato come UTC perché è solo la data ISO 8601.
Quindi, per rispondere direttamente alla domanda originale, l' "YYYY-MM-DD"
ECMA-262 deve essere interpretato come UTC, mentre l'altro è interpretato come locale. Ecco perchè:
Questo non produce risultati equivalenti:
console.log(new Date(Date.parse("Jul 8, 2005")).toString()); // Local
console.log(new Date(Date.parse("2005-07-08")).toString()); // UTC
Questo fa:
console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());
La linea di fondo è questa per l'analisi delle stringhe di date. L'UNICA stringa ISO 8601 che puoi analizzare in modo sicuro tra i browser è la forma lunga con un offset (± HH: mm o "Z"). Se lo fai, puoi tranquillamente andare avanti e indietro tra l'ora locale e l'ora UTC.
Funziona su tutti i browser (dopo IE9):
console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());
La maggior parte dei browser attuali tratta gli altri formati di input allo stesso modo, inclusi i "1/1/1970" (M / D / YYYY) utilizzati di frequente e "1/1/1970 00:00:00" (M / D / YYYY hh : mm: ss ap) formati. Tutti i seguenti formati (tranne l'ultimo) sono trattati come input di ora locale in tutti i browser. L'output di questo codice è lo stesso in tutti i browser nel mio fuso orario. L'ultimo è trattato come -05: 00 indipendentemente dal fuso orario dell'host perché l'offset è impostato nel timestamp:
console.log(Date.parse("1/1/1970"));
console.log(Date.parse("1/1/1970 12:00:00 AM"));
console.log(Date.parse("Thu Jan 01 1970"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00 GMT-0500"));
Tuttavia, poiché l'analisi anche dei formati specificati in ECMA-262 non è coerente, si consiglia di non fare mai affidamento sul parser incorporato e di analizzare sempre manualmente le stringhe, ad esempio utilizzando una libreria e fornire il formato al parser.
Ad esempio in moment.js potresti scrivere:
let m = moment('1/1/1970', 'M/D/YYYY');
Il lato di uscita
Sul lato output, tutti i browser traducono i fusi orari allo stesso modo ma gestiscono i formati di stringa in modo diverso. Ecco le toString
funzioni e ciò che producono. Notare che le funzioni toUTCString
e vengono toISOString
emesse 5:00 AM sulla mia macchina. Inoltre, il nome del fuso orario può essere un'abbreviazione e può essere diverso in diverse implementazioni.
Converte da UTC all'ora locale prima della stampa
- toString
- toDateString
- toTimeString
- toLocaleString
- toLocaleDateString
- toLocaleTimeString
Stampa l'ora UTC memorizzata direttamente
- toUTCString
- toISOString
In cromo
toString Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString Thu Jan 01 1970
toTimeString 00:00:00 GMT-05:00 (Eastern Standard Time)
toLocaleString 1/1/1970 12:00:00 AM
toLocaleDateString 1/1/1970
toLocaleTimeString 00:00:00 AM
toUTCString Thu, 01 Jan 1970 05:00:00 GMT
toISOString 1970-01-01T05:00:00.000Z
In Firefox
toString Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString Thu Jan 01 1970
toTimeString 00:00:00 GMT-0500 (Eastern Standard Time)
toLocaleString Thursday, January 01, 1970 12:00:00 AM
toLocaleDateString Thursday, January 01, 1970
toLocaleTimeString 12:00:00 AM
toUTCString Thu, 01 Jan 1970 05:00:00 GMT
toISOString 1970-01-01T05:00:00.000Z
Normalmente non utilizzo il formato ISO per l'input di stringhe. L'unica volta che l'utilizzo di quel formato è vantaggioso per me è quando le date devono essere ordinate come stringhe. Il formato ISO è ordinabile così com'è mentre gli altri no. Se è necessario disporre della compatibilità tra browser, specificare il fuso orario o utilizzare un formato di stringa compatibile.
Il codice new Date('12/4/2013').toString()
passa attraverso la seguente pseudo-trasformazione interna:
"12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"
Spero che questa risposta sia stata utile.