it-swarm.com.de

Warum liefert Date.parse falsche Ergebnisse?

Fall eins:

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

Ausgabe:

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

Fall zwei:

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

Ausgabe:

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


Warum ist die zweite Analyse falsch?

306
user121196

Bis zum Erscheinen der 5. Auflage war die Methode Date.parse vollständig implementierungsabhängig (new Date(string) ist äquivalent zu Date.parse(string) , es sei denn, letztere gibt eine Zahl statt einer Date zurück). In der 5. Ausgabe der Spezifikation wurde die Anforderung hinzugefügt, um ein vereinfachtes (und etwas falsch) zu unterstützen ISO-8601 (siehe auch Was sind gültige Datums-Zeitzeichenfolgen in JavaScript? ). Ansonsten gab es no, was Date.parsenew Date(string) akzeptieren sollte, außer dass sie die _/Date # toString -Ausgabe akzeptieren mussten (ohne zu sagen, was das war).

Ab ECMAScript 2017 (Ausgabe 8) mussten Implementierungen ihre Ausgabe für Date # toString und Date # toUTCString parsen, das Format dieser Zeichenfolgen wurde jedoch nicht angegeben.

Ab ECMAScript 2019 (Ausgabe 9) wurde das Format für Date # toString und Date # toUTCString als (jeweils) angegeben:

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

bereitstellung von 2 weiteren Formaten, die Date.parse zuverlässig in neuen Implementierungen analysieren sollte (wobei zu beachten ist, dass die Unterstützung nicht allgegenwärtig ist und nichtkonforme Implementierungen noch einige Zeit verwendet werden).

Ich würde empfehlen, Datumsstrings manuell zu analysieren und den Date-Konstruktor mit Argumenten für Jahr, Monat und Tag zu verwenden, um Mehrdeutigkeiten zu vermeiden:

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

Während ich kürzlich einen JS-Dolmetscher geschrieben habe, habe ich mich intensiv mit den inneren Abläufen von ECMA/JS-Dates auseinandergesetzt. Ich denke, ich werde hier meine 2 Cent einwerfen. Wir hoffen, dass das Teilen dieser Informationen anderen bei Fragen zu den Unterschieden zwischen Browsern beim Umgang mit Datumsangaben helfen kann.

Die Eingabeseite

Alle Implementierungen speichern ihre Datumswerte intern als 64-Bit-Zahlen, die die Anzahl der Millisekunden seit dem 1.1.1970 UTC darstellen (GMT entspricht UTC). Daten, die nach 1/1/1970 00:00:00 vorkommen, sind positive Zahlen und frühere Daten sind negativ.

Daher erzeugt der folgende Code auf allen Browsern genau das gleiche Ergebnis.

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

In meiner Zeitzone (EST) ist das Ergebnis 18000000, da dies die Anzahl ms in 5 Stunden ist (in Sommermonaten sind es nur 4 Stunden). Der Wert ist in verschiedenen Zeitzonen unterschiedlich. Alle großen Browser machen es auf dieselbe Weise.

Hier ist das Problem. Zwar gibt es einige Abweichungen in den Eingabezeichenfolgenformaten, die von den wichtigsten Browsern als Datumsangaben geparst werden, sie werden jedoch in Bezug auf Zeitzonen und Sommerzeit im Wesentlichen gleich interpretiert. Das eine ist das ISO 8601-Format. Es ist das einzige Format, das speziell in der Spezifikation ECMA-262 v.5 beschrieben wird. Bei allen anderen String-Formaten ist die Interpretation implementierungsabhängig. Ironischerweise ist dies das Format, in dem sich Browser unterscheiden können. Hier ist eine Vergleichsausgabe von Chrome im Vergleich zu Firefox für 1/1/1970 auf meinem Rechner im ISO 8601-Stringformat.

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
  • Der Spezifizierer "Z" gibt an, dass sich die Eingabe bereits in UTC-Zeit befindet und vor dem Speichern kein Offset erforderlich ist. 
  • Der Spezifizierer "-0500" gibt an, dass sich die Eingabe in GMT-05: 00 befindet. Daher interpretieren beide -Browser die Eingabe als in meiner lokalen Zeitzone. Das heißt, der Wert wird vor dem Speichern in UTC konvertiert. In meinem Fall bedeutet dies, dass der interne Wert des Datums um 18000000 ms erhöht wird, wodurch eine Verschiebung von -18000000ms (-05: 00) erforderlich ist, um mich auf die Ortszeit zurückzusetzen.
  • Wenn es jedoch keinen Bezeichner gibt, behandelt FF die Eingabe als Ortszeit, während Chrome Als UTC-Zeit behandelt wird. Für mich führt dies zu einer Differenz von 5 Stunden im gespeicherten Wert, was problematisch ist. In meiner Implementierung habe ich mich hier für FF entschieden, weil mir die Ausgabe von toString mit meinem Eingabewert übereinstimmt, es sei denn, ich gebe eine alternative Zeitzone an, die ich nie tue. Das abwesenheit eines Spezifizierers sollte die lokale Zeitangabe annehmen.

Aber hier wird es schlimmer. FF behandelt die Kurzform des ISO 8601-Formats ("JJJJ-MM-TT") anders als die Langform ("JJJJ-MM-DDTHH: mm: ss: sssZ") Kein logischer Grund. 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

Um die Frage des ursprünglichen Fragestellers direkt zu beantworten, ist "YYYY-MM-DD" die Kurzform des ISO 8601-Formats "YYYY-MM-DDTHH:mm:ss:sssZ". Es wird also als UTC-Zeit interpretiert, während der andere als lokal interpretiert wird. Deshalb, 

Das klappt nicht:

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

Das macht:

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

In der unteren Zeile werden Datumszeichenfolgen analysiert. Die EINZIGE ISO-8601-Zeichenfolge, die Sie sicher über Browser hinweg analysieren können, ist die lange Form. Verwenden Sie IMMER den Bezeichner "Z". Wenn Sie dies tun, können Sie sicher zwischen lokaler und UTC-Zeit hin und her wechseln.

Dies funktioniert in allen Browsern (nach IE9):

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

Glücklicherweise behandeln die meisten aktuellen Browser die anderen Eingabeformate gleich, einschließlich der am häufigsten verwendeten Formate "1/1/1970" und "1/1/1970 00:00:00 AM". Alle folgenden Formate (und andere) werden in allen Browsern als lokale Zeitangabe behandelt und vor dem Speichern in UTC konvertiert. Dadurch sind sie Cross-Browser-kompatibel. Die Ausgabe dieses Codes ist in allen Browsern meiner Zeitzone gleich.

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

Die Ausgabeseite

Auf der Ausgabeseite übersetzen alle Browser Zeitzonen auf dieselbe Weise, behandeln jedoch die Zeichenfolgenformate unterschiedlich. Hier sind die toString-Funktionen und was sie ausgeben. Beachten Sie, dass die Funktionen toUTCString und toISOString um 5:00 Uhr auf meinem Computer ausgegeben werden.

Konvertiert von UTC in Ortszeit vor dem Drucken

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

Gibt die gespeicherte UTC-Zeit direkt aus

 - toUTCString
 - toISOString 

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

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

Normalerweise verwende ich das ISO-Format nicht für die Eingabe von Zeichenketten. Die Verwendung dieses Formats ist für mich nur dann von Vorteil, wenn Datumsangaben als Zeichenfolgen sortiert werden müssen. Das ISO-Format ist wie gewohnt sortierbar, die anderen nicht. Wenn Sie browserübergreifend kompatibel sein müssen, geben Sie entweder die Zeitzone an oder verwenden Sie ein kompatibles Zeichenfolgenformat.

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.

181
drankin2112

Es gibt eine Methode für den Wahnsinn. Wenn ein Browser ein Datum als ISO-8601 interpretieren kann, ist dies in der Regel der Fall. "2005-07-08" fällt in dieses Lager und wird daher als UTC analysiert. "8. Juli 2005" kann nicht und daher wird es in der Ortszeit analysiert.

See JavaScript und Termine, was für ein Durcheinander! für mehr.

70
danvk

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

Diese Methode ist nützlich für Datumsangaben, die unusual formatiert sind.

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

Gemäß http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.html löst das Format "yyyy/mm/dd" die üblichen Probleme. .__ Er sagt: "Halten Sie sich immer an" JJJJ/MM/TT "für Ihre Datumszeichenfolgen. Es wird universell unterstützt und ist eindeutig. Bei diesem Format sind alle Zeiten lokal." Ich habe Tests gesetzt: http://jsfiddle.net/jlanus/ND2Qg/432/ Dieses Format: + vermeidet die Unstimmigkeit der Tages- und Monatsbestellungen durch Verwendung der ym d-Reihenfolge und einer vierstelligen Jahreszahl + vermeidet, dass das UTC-Problem mit dem lokalen Format nicht mit dem ISO-Format übereinstimmt, indem Schrägstriche verwendet werden + danvk, der dygraphs , sagt, dass dieses Format in allen Browsern gut ist. 

5
Juan Lanus

Verwenden Sie moment.js , um Daten zu analysieren:

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

Das dritte Argument bestimmt das strikte Parsing (verfügbar ab 2.3.0). Andernfalls kann moment.js falsche Ergebnisse liefern.

5
Lukasz Wiktor

Während CMS korrekt ist dass das Übergeben von Zeichenfolgen in die Parser-Methode im Allgemeinen unsicher ist, schlägt die neue ECMA-262 5th Edition (auch ES5) -Spezifikation in Abschnitt 15.9.4.2 vor, dass Date.parse() eigentlich ISO-formatierte Daten behandeln sollte . Die alte Spezifikation machte keinen solchen Anspruch. Natürlich bieten alte Browser und einige aktuelle Browser diese ES5-Funktionalität nicht an.

Dein zweites Beispiel ist nicht falsch. Dies ist das in UTC angegebene Datum, wie in Date.prototype.toISOString() angegeben, jedoch in Ihrer lokalen Zeitzone dargestellt.

4
peller

Diese leichte Datums-Parsing-Bibliothek sollte alle ähnlichen Probleme lösen. Ich mag die Bibliothek, weil sie einfach zu erweitern ist. Es ist auch möglich, es zu machen (nicht sehr geradeaus, aber nicht so schwer).

Parsing-Beispiel:

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

Und zurück zu string formatieren (Sie werden feststellen, dass beide Fälle genau das gleiche Ergebnis liefern):

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
Nux

Beide sind korrekt, werden jedoch als Datumsangaben mit zwei unterschiedlichen Zeitzonen interpretiert. Sie verglichen also Äpfel und Orangen:

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

Ich habe den Aufruf Date.parse() entfernt, da er automatisch für ein String-Argument verwendet wird. Ich habe auch die Daten im ISO8601-Format verglichen, damit Sie die Daten zwischen Ihren lokalen Daten und den UTC-Daten visuell vergleichen können. Die Zeiten liegen im Abstand von 7 Stunden. Dies ist der Zeitunterschied, weshalb bei Ihren Tests zwei unterschiedliche Daten angezeigt wurden.

Die andere Möglichkeit, dieselben lokalen/UTC-Daten zu erstellen, wäre:

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"

Ich empfehle jedoch weiterhin Moment.js , was einfach und dennoch mächtig ist:

// 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"
2
DJDaveMark

Hier ist ein kurzes, flexibles Snippet, um eine datetime-Zeichenfolge browserübergreifend zu konvertieren, wie von @ drankin2112 detailliert beschrieben.

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

Ihr Browser sollte das gleiche Zeitstempelergebnis wie Date.parse enthalten:

(new Date(tstring)).getTime()
2
Lorenz Lo Sauer

Die akzeptierte Antwort von CMS ist richtig, ich habe gerade einige Funktionen hinzugefügt:

  • eingabebereiche trimmen und säubern
  • schrägstriche, Bindestriche, Doppelpunkte und Leerzeichen analysieren
  • hat Standard Tag und Uhrzeit

// 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
}
0
Stephane L