Perché Date.parse fornisce risultati errati?

Caso 1:

new Date(Date.parse("Jul 8, 2005")); 

Produzione:

Ven 08 lug 2005 00:00:00 GMT-0700 (PST)

Caso due:

 new Date(Date.parse("2005-07-08")); 

Produzione:

Gio 07 lug 2005 17:00:00 GMT-0700 (PST)


Perché la seconda analisi è errata?

Fino all’uscita della quinta edizione, il metodo Date.parse completamente dall’implementazione (la new Date(string) è equivalente a Date.parse(string) tranne che quest’ultima restituisce un numero anziché una Date ). Nella quinta edizione spec è stato aggiunto il requisito per supportare una ISO-8601 semplificata (e leggermente errata) , ma a parte questo, non era necessario specificare quale Date.parse / new Date(string) dovesse accettare diversamente da quello che dovevano accetta qualsiasi output Date # toString (senza dire cosa fosse).

A partire da ECMAScript 2017 (edizione 8), le implementazioni sono state necessarie per analizzare il loro output per Date # toString e Date # toUTCString , ma il formato di quelle stringhe non è stato specificato.

A partire da ECMAScript 2019 (edizione 9) il formato per Date # toString e Date # toUTCString , sono stati specificati come (rispettivamente):

  1. ddd MMM GG AAAA HH: mm: ss ZZ [(nome fuso orario)]
    es. mar 10 lug 2018 18:39:58 GMT + 0530 (IST)
  2. ddd, GG MMM AAAA HH: mm: ss Z
    es. mar 10 lug 2018 13:09:58 GMT

fornire altri due formati che Date.parse dovrebbe analizzare in modo affidabile nelle nuove implementazioni (osservando che il supporto non è onnipresente e le implementazioni non conformi rimarranno in uso per qualche tempo).

Vorrei raccomandare che le stringhe di data vengano analizzate manualmente e il costruttore di date utilizzato con gli argomenti di anno, mese e giorno per evitare ambiguità:

 // parse a date in yyyy-mm-dd format function parseDate(input) { var parts = input.split('-'); // new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]]) return new Date(parts[0], parts[1]-1, parts[2]); // Note: months are 0-based } 

Durante la recente esperienza di scrittura di un interprete JS ho lottato molto con il funzionamento interno delle date di ECMA / JS. Quindi, immagino che inserirò i miei 2 centesimi qui. Spero che condividere questa roba aiuterà gli altri a rispondere alle domande sulle differenze tra i browser nel modo in cui gestiscono le date.

Il lato di input

Tutte le implementazioni memorizzano internamente i loro valori di data come numeri a 64 bit che rappresentano il numero di millisecondi dal 1/1/1970 UTC (GMT è la stessa cosa di UTC). Le date che si verificano dopo 1/1/1970 00:00:00 sono numeri positivi e le date precedenti sono negative.

Pertanto, il codice seguente produce lo stesso risultato esatto su tutti i browser.

 Date.parse('1/1/1970'); 

Nel mio fuso orario (EST), il risultato è 18000000 perché è il numero di ms in 5 ore (sono solo 4 ore durante i mesi dell’ora legale). Il valore sarà diverso in diversi fusi orari. Tutti i principali browser lo fanno allo stesso modo.

Ecco però il problema. Mentre c’è una certa varianza nei formati di stringa di input che i principali browser analizzeranno come date, in pratica li interpretano allo stesso modo dei fusi orari e dell’ora legale. Il primo è il formato ISO 8601. È l’unico formato delineato specificamente nella specifica ECMA-262 v.5. Per tutti gli altri formati di stringa, l’interpretazione dipende dall’implementazione. Ironia della sorte, questo è il formato in cui i browser possono essere diversi. Ecco un output di confronto di Chrome vs Firefox per 1/1/1970 sulla mia macchina utilizzando il formato di stringa ISO 8601.

 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 
  • L’identificatore “Z” indica che l’input è già in ora UTC e non richiede alcun offset prima della memorizzazione.
  • L’identificatore “-0500” indica che l’ingresso è in GMT-05: 00, quindi entrambi i browser interpretano l’input come nel fuso orario locale. Ciò significa che il valore viene convertito in UTC prima di essere memorizzato. Nel mio caso, significa aggiungere 18000000 ms al valore interno della data, quindi richiedere uno spostamento di -18000000ms (-05: 00) per riportarmi all’ora locale.
  • Tuttavia, quando non esiste alcun identificatore, FF considera l’input come ora locale, mentre Chrome lo considera come ora UTC. Per me questo crea una differenza di 5 ore nel valore memorizzato, che è problematico. Nella mia implementazione ho finito per schierarmi con FF qui perché mi piace l’output di toString per far corrispondere il mio valore di input a meno che non specifichi un fuso orario alternativo, che non faccio mai. L’ assenza di un specificatore dovrebbe presumere l’input di tempo locale.

Ma qui è dove va peggio, FF tratta la forma breve del formato ISO 8601 (“AAAA-MM-GG”) in modo diverso da come tratta il formato lungo (“AAAA-MM-GGTHH: mm: ss: sssZ”) per nessuna ragione logica di sorta. 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, per rispondere direttamente alla domanda del richiedente originale, "YYYY-MM-DD" è la forma abbreviata del formato ISO 8601 "YYYY-MM-DDTHH:mm:ss:sssZ" . Quindi, viene interpretato come ora UTC mentre l’altro viene interpretato come locale. Ecco perchè,

Questo non jive:

 console.log(new Date(Date.parse("Jul 8, 2005")).toString()); console.log(new Date(Date.parse("2005-07-08")).toString()); 

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 le stringhe di data di analisi. L’unica stringa ISO 8601 che puoi analizzare in modo sicuro attraverso i browser è la forma lunga. E, utilizzare SEMPRE l’identificatore “Z”. Se lo fai puoi tranquillamente andare avanti e indietro tra ora locale e ora UTC.

Funziona su tutti i browser (dopo IE9):

 console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString()); 

Fortunatamente, la maggior parte dei browser attuali considera gli altri formati di input allo stesso modo, inclusi i formati più utilizzati “1/1/1970” e “1/1/1970 00:00:00 AM”. Tutti i seguenti formati (e altri) sono trattati come input di tempo locale in tutti i browser e convertiti in UTC prima dello storage. Quindi, rendendoli compatibili cross-browser. L’output di questo codice è lo stesso in tutti i browser nel mio fuso orario.

 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")); 

Il lato di uscita

Dal lato dell’output, tutti i browser traducono i fusi orari allo stesso modo ma gestiscono diversamente i formati delle stringhe. Ecco le funzioni toString e cosa producono. Notare le funzioni toUTCString e toISOString uscita alle 5:00 AM sulla mia macchina.

Converte da UTC a Locale prima della stampa

  - toString - toDateString - toTimeString - toLocaleString - toLocaleDateString - toLocaleTimeString 

Stampa direttamente l’ora UTC memorizzata

  - toUTCString - toISOString 

  In Chrome 
 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 uso il formato ISO per l’input delle stringhe. L’unica volta che l’utilizzo di tale formato mi è vantaggioso è 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 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.

C’è un metodo per la pazzia. Come regola generale, se un browser può interpretare una data come ISO-8601, lo farà. “2005-07-08” rientra in questo campo e quindi viene analizzato come UTC. “8 luglio 2005” non può, e quindi viene analizzato nell’ora locale.

Vedi JavaScript e Date, What a Mess! per più.

Un’altra soluzione consiste nel creare un array associativo con il formato della data e quindi riformattare i dati.

Questo metodo è utile per la data formattata in modo non casuale.

Un esempio:

  mydate='01.02.12 10:20:43': myformat='dd/mm/yy HH:MM:ss'; dtsplit=mydate.split(/[\/ .:]/); dfsplit=myformat.split(/[\/ .:]/); // creates assoc array for date df = new Array(); for(dc=0;dc<6;dc++) { df[dfsplit[dc]]=dtsplit[dc]; } // uses assc array for standard mysql format dstring[r] = '20'+df['yy']+'-'+df['mm']+'-'+df['dd']; dstring[r] += ' '+df['HH']+':'+df['MM']+':'+df['ss']; 

Secondo http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.html il formato “aaaa / mm / gg” risolve i soliti problemi. Dice: “Attacca a” AAAA / MM / GG “per le stringhe di data quando è ansible.È universalmente supportato e non ambiguo.In questo formato, tutti i tempi sono locali.” Ho impostato i test: http://jsfiddle.net/jlanus/ND2Qg/432/ Questo formato: + evita l’ambiguità dell’ordine del giorno e del mese utilizzando l’ordinamento ymd e un anno a 4 cifre + evita l’UTC rispetto al problema locale rispettando il formato ISO usando le slash + danvk, il ragazzo dei dygraphs , dice che questo formato è buono in tutti i browser.

Mentre CMS è corretto che passare le stringhe nel metodo di analisi è in genere non sicuro, la nuova specifica ECMA-262 5th Edition (nota come ES5) nella sezione 15.9.4.2 suggerisce che Date.parse() realtà dovrebbe gestire le date con formattazione ISO. Le vecchie specifiche non reclamavano tale affermazione. Naturalmente, i vecchi browser e alcuni browser correnti non forniscono ancora questa funzionalità ES5.

Il tuo secondo esempio non è sbagliato. È la data specificata in UTC, come implicita in Date.prototype.toISOString() , ma è rappresentata nel fuso orario locale.

Usa moment.js per analizzare le date:

 var caseOne = moment("Jul 8, 2005", "MMM D, YYYY", true).toDate(); var caseTwo = moment("2005-07-08", "YYYY-MM-DD", true).toDate(); 

Il terzo argomento determina l’analisi rigorosa (disponibile come da 2.3.0). Senza di esso moment.js può anche dare risultati errati.

Questa libreria di analisi delle date leggera dovrebbe risolvere tutti i problemi simili. Mi piace la libreria perché è abbastanza facile da estendere. È anche ansible utilizzarlo (non molto semplice, ma non così difficile).

Esempio di analisi:

 var caseOne = Date.parseDate("Jul 8, 2005", "M d, Y"); var caseTwo = Date.parseDate("2005-07-08", "Ymd"); 

E la formattazione torna alla stringa (noterai che entrambi i casi danno esattamente lo stesso risultato):

 console.log( caseOne.dateFormat("M d, Y") ); console.log( caseTwo.dateFormat("M d, Y") ); console.log( caseOne.dateFormat("Ymd") ); console.log( caseTwo.dateFormat("Ymd") ); 

Ecco uno snippet breve e flessibile per convertire una stringa datetime in modalità cross-browser-safe come descritta da @ drankin2112.

 var inputTimestamp = "2014-04-29 13:00:15"; //example var partsTimestamp = inputTimestamp.split(/[ \/:-]/g); if(partsTimestamp.length < 6) { partsTimestamp = partsTimestamp.concat(['00', '00', '00'].slice(0, 6 - partsTimestamp.length)); } //if your string-format is something like '7/02/2014'... //use: var tstring = partsTimestamp.slice(0, 3).reverse().join('-'); var tstring = partsTimestamp.slice(0, 3).join('-'); tstring += 'T' + partsTimestamp.slice(3).join(':') + 'Z'; //configure as needed var timestamp = Date.parse(tstring); 

Il tuo browser dovrebbe fornire lo stesso risultato timestamp di Date.parse con:

 (new Date(tstring)).getTime() 

Entrambi sono corretti, ma vengono interpretati come date con due fusi orari diversi. Quindi hai confrontato mele e arance:

 // local dates new Date("Jul 8, 2005").toISOString() // "2005-07-08T07:00:00.000Z" new Date("2005-07-08T00:00-07:00").toISOString() // "2005-07-08T07:00:00.000Z" // UTC dates new Date("Jul 8, 2005 UTC").toISOString() // "2005-07-08T00:00:00.000Z" new Date("2005-07-08").toISOString() // "2005-07-08T00:00:00.000Z" 

Ho rimosso la chiamata Date.parse() poiché è stata utilizzata automaticamente su un argomento stringa. Ho anche confrontato le date utilizzando il formato ISO8601 in modo da poter confrontare visivamente le date tra le date locali e le date UTC. I tempi sono a 7 ore di distanza, che è la differenza di fuso orario e perché i test hanno mostrato due date diverse.

L’altro modo di creare queste stesse date locali / UTC sarebbe:

 new Date(2005, 7-1, 8) // "2005-07-08T07:00:00.000Z" new Date(Date.UTC(2005, 7-1, 8)) // "2005-07-08T00:00:00.000Z" 

Ma continuo a consigliare vivamente Moment.js che è semplice ma potente :

 // parse string moment("2005-07-08").format() // "2005-07-08T00:00:00+02:00" moment.utc("2005-07-08").format() // "2005-07-08T00:00:00Z" // year, month, day, etc. moment([2005, 7-1, 8]).format() // "2005-07-08T00:00:00+02:00" moment.utc([2005, 7-1, 8]).format() // "2005-07-08T00:00:00Z" 

La risposta accettata da CMS è corretta, ho appena aggiunto alcune funzionalità:

  • tagliare e pulire gli spazi di input
  • analizza barre, trattini, due punti e spazi
  • ha giorno e ora predefiniti

 // parse a date time that can contains spaces, dashes, slashes, colons function parseDate(input) { // trimes and remove multiple spaces and split by expected characters var parts = input.trim().replace(/ +(?= )/g,'').split(/[\s-\/:]/) // new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]]) return new Date(parts[0], parts[1]-1, parts[2] || 1, parts[3] || 0, parts[4] || 0, parts[5] || 0); // Note: months are 0-based }