Perché Date.parse dà risultati errati?


344

Caso uno:

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 non è corretta?


30
Il secondo analisi non è di per sé errato, è solo che il primo viene analizzato in ora locale e il secondo in UTC. Si noti che "Giovedì 07 luglio 2005 17:00:00 GMT-0700 (PST)" è lo stesso di "2005-07-08 00:00".
jches


18

1
Nel caso in cui qualcuno è venuto qui per capire perché una data sta tornando NaNin Firefox, ho scoperto che la maggior parte degli altri browser (e Node.js) analizzerà una data senza un giorno, come "Aprile 2014" come 1 aprile 2014, ma Firefox restituisce NaN. Devi passare una data corretta.
Jazzy,

Per aggiungere al commento di Jason sopra: Se stai ricevendo una NaN in Firefox, un altro problema potrebbe essere che Firefox e Safari non amano le date trattate. Solo Chrome lo fa. Utilizzare invece una barra.
Bangkok,

Risposte:


451

Fino alla pubblicazione della quinta edizione, il Date.parsemetodo era completamente dipendente dall'implementazione ( new Date(string)equivale ad Date.parse(string)eccezione del fatto che quest'ultimo restituisca un numero anziché un Date). Nella specifica della 5a edizione è stato aggiunto il requisito per supportare una ISO-8601 semplificata (e leggermente errata) (vedere anche Cosa sono le stringhe di data e ora valide in JavaScript? ). Ma a parte questo, non c'era alcun requisito per cosa Date.parse/ new Date(string)dovevano accettare oltre a quello che dovevano accettare qualunque Date#toStringoutput (senza dire cosa fosse).

A partire da ECMAScript 2017 (edizione 8), le implementazioni erano necessarie per analizzare il loro output per Date#toStringe Date#toUTCString, ma il formato di tali stringhe non era specificato.

A partire da ECMAScript 2019 (edizione 9) il formato per Date#toStringe Date#toUTCString, è stato specificato come (rispettivamente):

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

fornendo altri 2 formati che Date.parsedovrebbero essere analizzati 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 date vengano analizzate manualmente e il costruttore Date utilizzato con argomenti di anno, mese e giorno per evitare ambiguità:

// parse a date in yyyy-mm-dd format
function parseDate(input) {

  let 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
}

Eccellente, ho dovuto usare questo in quanto Date.parsenon si comportava con i formati di data nel Regno Unito per qualche motivo che non riuscivo a capire
Ben

1
Le parti temporali sono documentate nel codice @CMS. Ho usato questo codice con un formato data di "2012-01-31 12:00:00" return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);Funziona perfettamente, grazie!
Richard Rout,

1
@CMS cosa intendi per implementazione dipendente ?
Royi Namir,

3
@RoyiNamir, significa che i risultati dipendono dal browser Web (o altra implementazione JavaScript) che esegue il tuo codice.
Samuel Edwin Ward,

1
Ho anche avuto problemi con la nuova Data (stringa) in diversi browser che si comportavano diversamente. Non è nemmeno una questione se si rompe nelle vecchie versioni di IE, i diversi browser non sono coerenti. Non usare mai Date.parse o new Date (string) mai.
Hoffmann,

195

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 toStringfunzioni e ciò che producono. Notare che le funzioni toUTCStringe vengono toISOStringemesse 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.


3
Prima di tutto, questo è un fantastico risveglio. Volevo sottolineare una dipendenza, tuttavia. Per quanto riguarda gli specificatori del fuso orario, hai dichiarato: "L'assenza di un specificatore dovrebbe presumere l'immissione dell'ora locale". Per fortuna, lo standard ECMA-262 elimina qualsiasi necessità di presunzione. Indica : "Il valore di un offset di fuso orario assente è" Z "." Pertanto, si presume che una stringa data / ora senza un fuso orario specificato sia un UTC anziché l'ora locale. Naturalmente, come per molte altre cose di JavaScript, sembra esserci un piccolo accordo tra le implementazioni.
Daniel,

2
... compresi i formati "1/1/1970" e "1/1/1970 00:00:00" utilizzati più frequentemente. - usato più frequentemente dove ? Non è nel mio paese, sicuramente.
Ulidtko,

2
@ulidtko - Scusa, sono negli Stati Uniti. Wow ... sei proprio lì a Kiev. Spero che tu e la tua famiglia rimarrete al sicuro e che le cose si stabilizzeranno presto. Abbi cura di te e buona fortuna con tutto.
drankin2112,

Solo una nota qui. Sembra che questo non funzioni per i browser Safari (cioè iOS o OSX). Quello o ho qualche altro problema in corso.
keyneom,

1
@ Daniel: per fortuna gli autori di ECMAScript hanno corretto il loro errore rispetto al fuso orario mancante per le rappresentazioni di data e ora. Ora le stringhe di data e ora senza fuso orario utilizzano l'offset del fuso orario host (ovvero "locale"). Confusamente, la data ISO 8601 solo i moduli sono trattati come UTC (anche se non è particolarmente chiaro dalle specifiche), mentre ISO 8601 li tratta come locali, quindi non hanno risolto tutto.
RobG,

70

C'è un metodo per la follia. 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, che casino! per più.


3
" Come regola generale, se un browser è in grado di interpretare una data come ISO-8601, lo farà ". "2020-03-20 13:30:30" è trattato come ISO 8601 e locale da molti browser, ma Data non valida di Safari. Esistono molti formati ISO 8601 che non sono supportati dalla maggior parte dei browser, ad esempio 2004-W53-7 e 2020-092.
RobG

7

Un'altra soluzione è quella di creare un array associativo con formato data e quindi riformattare i dati.

Questo metodo è utile per la data formattata in modo insolito.

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'];

5

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 dalla 2.3.0). Senza di essa moment.js potrebbe anche dare risultati errati.


4

Secondo http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.html il formato "yyyy / mm / dd" risolve i soliti problemi. Dice: "Attenersi a" AAAA / MM / GG "per le stringhe di date ogni volta che è possibile. È universalmente supportato e non ambiguo. Con 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 il problema UTC vs. locale conformarsi al formato ISO usando slash + danvk, il ragazzo dei dygraph , afferma che questo formato è buono in tutti i browser.


Potresti voler vedere la risposta dell'autore .
Brad Koch,

Direi che la soluzione con l'esempio in jsFiddle funziona abbastanza bene se usi jQuery in quanto usa il parser datepicker. Nel mio caso il problema è con jqGrid, ma ho scoperto che ha il suo metodo parseDate. Ma comunque l'esempio mi ha aiutato dandomi un'idea quindi +1 per questo, grazie.
Vasil Popov

2
Quell'articolo sui dygraph è sbagliato e il primo esempio sulla pagina illustra chiaramente perché usare le barre anziché i trattini è davvero un cattivo consiglio. Al momento della stesura dell'articolo, l'utilizzo di "2012/03/13" ha comportato l'analisi del browser come data locale, anziché UTC. La specifica ECMAScript definisce il supporto esplicito per l'utilizzo di "AAAA-MM-GG" (ISO8601), quindi utilizzare sempre trattini. Va notato che al momento in cui scrivo questo commento, Chrome è stato corretto per trattare le barre come UTC.
Lachlan Hunt,

4

Mentre CMS ha ragione nel dire che passare stringhe nel metodo di analisi non è generalmente sicuro, la nuova specifica ECMA-262 5th Edition (aka ES5) nella sezione 15.9.4.2 suggerisce che in Date.parse()realtà dovrebbe gestire le date in formato ISO. Le vecchie specifiche non hanno fatto affermazioni del genere. Naturalmente, i vecchi browser e alcuni browser attuali non forniscono ancora questa funzionalità ES5.

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


1
E l'analisi delle stringhe di date è stata nuovamente modificata in ECMAScript 2015 in modo che "2005-07-08" sia locale, non UTC. A proposito, ES5 non era lo standard fino a giugno 2011 (e attualmente lo è ECMAScript 2015). ;-)
RobG

1
Per confondere le cose, TC39 ha deciso ottobre (appena un mese dopo il mio precedente post) che "2005-07-08" dovrebbe essere UTC , tuttavia "" 2005-07-08T00: 00: 00 "dovrebbe essere locale. Entrambi sono ISO 8601 formati conformi, entrambi sono senza fuso orario, ma sono trattati in modo diverso. Vai a capire.
RobG

2

Questa libreria di analisi della data leggera dovrebbe risolvere tutti i problemi simili. Mi piace la biblioteca perché è abbastanza facile da estendere. È anche possibile farlo (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", "Y-m-d");

E formattazione di nuovo in 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("Y-m-d") );
console.log( caseTwo.dateFormat("Y-m-d") );

2

Ecco uno snippet breve e flessibile per convertire una stringa datetime in un modo sicuro per più browser, come descritto in dettaglio 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 data / ora Date.parsedi:

(new Date(tstring)).getTime()

Suggerirei di aggiungere T al regex per catturare anche le date già formattate in JS: inputTimestamp.split (/ [T \ /: -] / g)
andig

Se si divide la stringa in parti componenti, il passaggio successivo più affidabile consiste nell'utilizzare tali parti come argomenti per il costruttore Date. La creazione di un'altra stringa da dare al parser ti riporta al passaggio 1. "2014-04-29 13:00:15" deve essere analizzato come locale, il tuo codice lo riformatta come UTC. :-(
RobG

2

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 Date.parse()chiamata poiché viene 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 distanti 7 ore, 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 consiglio vivamente Moment.js che è altrettanto 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"

0

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

  • tagliare e pulire gli spazi di input
  • analizzare 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
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.