406 Stimmen

Warum liefert Date.parse falsche Ergebnisse?

Fall eins:

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

Sortie :

Fri Jul 08 2005 00:00:00 GMT-0700 (PST)

Fall 2:

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

Sortie :

Thu Jul 07 2005 17:00:00 GMT-0700 (PST)


Warum ist das zweite Parsing falsch?

489voto

Christian C. Salvadó Punkte 763569

Bis zum Erscheinen der 5. Auflage der Spezifikation war die Date.parse Methode war vollständig Implementierungsabhängig ( new Date(string) ist gleichbedeutend mit Date.parse(string) mit der Ausnahme, dass letztere eine Zahl anstelle einer Date ). In der Spezifikation der 5. Auflage wurde die Anforderung hinzugefügt, eine vereinfacht (und leicht falsch) ISO-8601 (siehe auch Was sind gültige Date Time Strings in JavaScript? ). Aber abgesehen davon gab es no Anforderung für was Date.parse / new Date(string) akzeptieren sollten, als dass sie das akzeptieren mussten, was Date#toString Output (ohne zu sagen, was das war).

Ab ECMAScript 2017 (Edition 8) müssen Implementierungen ihre Ausgabe für Date#toString et Date#toUTCString aber das Format dieser Zeichenketten wurde nicht angegeben.

Ab ECMAScript 2019 (Edition 9) wird das Format für Date#toString et Date#toUTCString wurden als (jeweils) angegeben:

  1. ddd MMM DD YYYY HH:mm:ss ZZ [(Name der Zeitzone)]
    z.B. Tue Jul 10 2018 18:39:58 GMT+0530 (IST)
  2. ddd, DD MMM YYYY HH:mm:ss Z
    z.B. Tue 10 Jul 2018 13:09:58 GMT

mit 2 weiteren Formaten, die Date.parse sollte in neuen Implementierungen zuverlässig geparst werden (wobei zu beachten ist, dass die Unterstützung nicht allgegenwärtig ist und nicht konforme Implementierungen noch einige Zeit in Gebrauch sein werden).

Ich würde empfehlen, dass Datumsstrings manuell geparst werden und die Datum-Konstruktor mit den Argumenten Jahr, Monat und Tag verwendet, um Mehrdeutigkeiten zu vermeiden:

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

234voto

drankin2112 Punkte 4615

Während meiner jüngsten Erfahrungen beim Schreiben eines JS-Interpreters habe ich viel mit dem Innenleben der ECMA/JS-Daten gerungen. Also, ich denke, ich werde in meine 2 Cent hier werfen. Ich hoffe, dass die Weitergabe dieser Informationen anderen helfen wird, die Fragen zu den Unterschieden zwischen den Browsern bei der Handhabung von Datumsangaben haben.

Die Eingabeseite

Alle Implementierungen speichern ihre Datumswerte intern als 64-Bit-Zahlen, die die Anzahl der Millisekunden (ms) seit dem 01.01.1970 UTC darstellen (GMT ist das Gleiche wie UTC). Dieses Datum ist die ECMAScript-Epoche, die auch von anderen Sprachen wie Java und POSIX-Systemen wie UNIX verwendet wird. Daten, die nach der Epoche liegen, sind positive Zahlen, Daten davor sind negativ.

Der folgende Code wird in allen aktuellen Browsern als dasselbe Datum interpretiert, allerdings mit dem Offset der lokalen Zeitzone:

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

In meiner Zeitzone (EST, also -05:00) ist das Ergebnis 18000000, denn so viele ms sind in 5 Stunden (in den Sommermonaten sind es nur 4 Stunden). Der Wert wird in verschiedenen Zeitzonen unterschiedlich sein. Dieses Verhalten ist in der ECMA-262 festgelegt, so dass alle Browser dies auf die gleiche Weise tun.

Es gibt zwar einige Unterschiede bei den Formaten der Eingabezeichenfolgen, die von den wichtigsten Browsern als Datumsangaben interpretiert werden, aber im Wesentlichen interpretieren sie sie in Bezug auf Zeitzonen und Sommerzeit gleich, auch wenn das Parsing weitgehend von der Implementierung abhängt.

Das Format ISO 8601 ist jedoch anders. Es ist eines von nur zwei Formaten in ECMAScript 2015 (Ausgabe 6), die von allen Implementierungen auf die gleiche Weise geparst werden müssen (das andere ist das Format, das für Date.prototype.toString ).

Aber selbst bei Zeichenketten im ISO 8601-Format gibt es Implementierungen, die es falsch machen. Hier ist eine Vergleichsausgabe von Chrome und Firefox, als diese Antwort ursprünglich für den 1.1.1970 (die Epoche) auf meinem Rechner unter Verwendung von Zeichenketten im ISO 8601-Format geschrieben wurde, die debe in allen Implementierungen auf genau den gleichen Wert geparst werden:

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
  • Im ersten Fall gibt der "Z"-Spezifizierer an, dass die Eingabe in UTC-Zeit erfolgt, also nicht von der Epoche abweicht, und das Ergebnis ist 0
  • Im zweiten Fall bedeutet die Angabe "-0500", dass die Eingabe in GMT-05:00 erfolgt, und beide Browser interpretieren die Eingabe als in der Zeitzone -05:00. Das bedeutet, dass der UTC-Wert von der Epoche versetzt ist, was bedeutet, dass 18000000ms zum internen Zeitwert des Datums hinzugefügt werden.
  • Der dritte Fall, in dem es keinen Spezifizierer gibt, debe für das Hostsystem als lokal behandelt werden. FF behandelt die Eingabe korrekt als Ortszeit, während Chrome sie als UTC behandelt, was zu unterschiedlichen Zeitwerten führt. Bei mir führt dies zu einem Unterschied von 5 Stunden im gespeicherten Wert, was problematisch ist. Andere Systeme mit anderen Offsets werden andere Ergebnisse erhalten.

Dieser Unterschied wurde ab 2020 behoben, aber es gibt noch andere Macken zwischen den Browsern beim Parsen von Zeichenketten im ISO 8601-Format.

Aber es kommt noch schlimmer. Eine Besonderheit der ECMA-262 besteht darin, dass das reine ISO 8601-Datumsformat (JJJJ-MM-TT) als UTC geparst werden muss, während es nach ISO 8601 als Lokalformat geparst werden muss. Hier ist die Ausgabe von FF mit den langen und kurzen ISO-Datumsformaten ohne Zeitzonenangabe.

Date.parse('1970-01-01T00:00:00');       // 18000000
Date.parse('1970-01-01');                // 0

Die erste wird also als Ortszeit geparst, weil es sich um ISO 8601 Datum und Uhrzeit ohne Zeitzone handelt, und die zweite wird als UTC geparst, weil es sich nur um ein ISO 8601 Datum handelt.

Um also die ursprüngliche Frage direkt zu beantworten, "YYYY-MM-DD" muss gemäß ECMA-262 als UTC interpretiert werden, während der andere als lokal interpretiert wird. Das ist der Grund:

Dies führt nicht zu gleichwertigen Ergebnissen:

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

Das stimmt:

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

Die Quintessenz für das Parsen von Datumszeichenfolgen ist die folgende. Die EINZIGE ISO 8601-Zeichenkette, die Sie sicher in allen Browsern analysieren können, ist die lange Form mit einem Versatz (entweder ±HH:mm oder "Z"). Auf diese Weise können Sie sicher zwischen Ortszeit und UTC-Zeit hin- und herwechseln.

Dies funktioniert in allen Browsern (ab IE9):

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

Die meisten aktuellen Browser behandeln die anderen Eingabeformate gleich, einschließlich der häufig verwendeten Formate "1/1/1970" (M/D/YYYY) und "1/1/1970 00:00:00 AM" (M/D/YYYY hh:mm:ss ap). Alle folgenden Formate (außer dem letzten) werden in allen Browsern als Eingabe der Ortszeit behandelt. Die Ausgabe dieses Codes ist in allen Browsern in meiner Zeitzone gleich. Das letzte Format wird unabhängig von der Zeitzone des Hosts als -05:00 behandelt, da der Offset im Zeitstempel festgelegt ist:

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

Da jedoch selbst die in ECMA-262 spezifizierten Formate nicht einheitlich geparst werden, wird empfohlen, sich nie auf den eingebauten Parser zu verlassen und Zeichenketten immer manuell zu parsen, z. B. mithilfe einer Bibliothek, und dem Parser das Format zu übergeben.

In moment.js könnten Sie z.B. schreiben:

let m = moment('1/1/1970', 'M/D/YYYY'); 

Die Ausgabeseite

Auf der Ausgabeseite übersetzen alle Browser die Zeitzonen auf die gleiche Weise, aber sie behandeln die String-Formate unterschiedlich. Hier sind die toString Funktionen und was sie ausgeben. Beachten Sie die toUTCString et toISOString Funktionen geben auf meinem Rechner 5:00 Uhr morgens aus. Außerdem kann der Name der Zeitzone eine Abkürzung sein und in verschiedenen Implementierungen anders lauten.

Konvertiert vor dem Drucken von UTC in die lokale Zeit

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

Druckt die gespeicherte UTC-Zeit direkt aus

 - 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

Ich verwende normalerweise nicht das ISO-Format für die Eingabe von Zeichenketten. Das einzige Mal, dass dieses Format für mich von Vorteil ist, ist, wenn Daten als Zeichenketten sortiert werden müssen. Das ISO-Format ist ohne weiteres sortierbar, die anderen Formate nicht. Wenn Sie eine browserübergreifende Kompatibilität benötigen, geben Sie entweder die Zeitzone an oder verwenden Sie ein kompatibles String-Format.

Der Code new Date('12/4/2013').toString() durchläuft die folgende interne Pseudo-Transformation:

  "12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"

Ich hoffe, diese Antwort war hilfreich.

75voto

danvk Punkte 14538

Der Wahnsinn hat Methode. Generell gilt: Wenn ein Browser ein Datum als ISO-8601 interpretieren kann, tut er das auch. "2005-07-08" fällt in dieses Lager und wird daher als UTC geparst. "8. Juli 2005" kann das nicht und wird daher in der lokalen Zeit geparst.

Voir JavaScript und Daten, was für ein Chaos! für mehr.

8voto

guido rizzi Punkte 71

Eine andere Lösung besteht darin, ein assoziatives Array mit Datumsformat zu erstellen und die Daten dann neu zu formatieren.

Diese Methode ist nützlich für ungewöhnlich formatierte Daten.

Ein Beispiel:

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

8voto

Lukasz Wiktor Punkte 18427

Utilisez moment.js zum Parsen von Daten:

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

Das 3. Argument bestimmt strict parsing (verfügbar ab 2.3.0). Ohne dieses Argument kann moment.js auch falsche Ergebnisse liefern.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X