Date.parseが誤った結果を返すのはなぜですか?


344

ケース1:

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

出力:

2005年7月8日金00:00:00 GMT-0700(PST)

ケース2:

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

出力:

2005年7月7日(木)17:00:00 GMT-0700(PST)


2番目の解析が正しくないのはなぜですか?


30
2番目の解析自体は正しくありません。最初の解析は現地時間で、2番目の解析はUTCで行われるだけです。「2005年7月7日木曜日17:00:00 GMT-0700(PST)」は「2005-07-08 00:00」と同じです。
jches


18

1
NaNFirefoxで日付が返された理由を理解するために誰かがここに来た場合、他のほとんどのブラウザー(およびNode.js)は、2014年4月1日として「2014年4月」のように、日付なしで日付を解析しますが、Firefox NaNを返します。適切な日付を渡す必要があります。
Jazzy

上記のJasonのコメントに追加するには:FirefoxでNaNを受け取っている場合、FirefoxとSafariがハイフン付きの日付を好まないことが別の問題である可能性があります。Chromeのみです。代わりにスラッシュを使用してください。
バンコクの2015

回答:


451

第5版の仕様が出るまで、Date.parseメソッドは完全に実装に依存していました(後者はaではなく数値を返すnew Date(string)ことDate.parse(string)を除いて同等Dateです)。要件仕様第5版ではサポートするために追加された簡素化(およびやや不正確な) ISO-8601(も参照の有効な日付JavaScriptで時刻文字列は何?)。しかし、それ以外に、ありませんでした何の何のための要件Date.parse/ new Date(string)何でも、彼らは受け入れなければならなかったこと以外に受け入れるべきDate#toString出力(だったこと何も言わずに)。

ECMAScriptの2017(8版)のように、実装は、ため、それらの出力を解析するために必要であったDate#toStringDate#toUTCStringするが、それらの文字列の形式が指定されませんでした。

ECMAScript 2019(エディション9)以降、Date#toStringおよびの形式はDate#toUTCString(それぞれ)として指定されています。

  1. ddd MMM DD YYYY HH:mm:ss ZZ [(timezone name)]
    例:2018年7月10日火曜日18:39:58 GMT + 0530(IST)
  2. ddd、DD MMM YYYY HH:mm:ss Z
    例:2018年7月10日火曜日13:09:58 GMT

Date.parse新しい実装で確実に解析する必要のある2つのフォーマットをさらに提供します(サポートはユビキタスではなく、非準拠の実装はしばらく使用され続けることに注意してください)。

あいまいさを避けるために、日付文字列を手動で解析し、Dateコンストラクターを年、月、日の引数と共に使用することをお勧めします。

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

すばらしい、Date.parseなんらかの理由で英国の日付形式で動作しないため、これを使用する必要がありました
Ben

1
時間の部分は@CMSコードで文書化されています。このコードを「2012-01-31 12:00:00」の日付形式で使用し return new Date(parts[0], parts[1] - 1, parts[2], parts[3], parts[4], parts[5]);ました。申し分なく機能します。
Richard Rout、2013

1
@CMS 実装依存とはどういう意味 ですか?
Royi Namir 2013

3
@RoyiNamir、それは結果がコードを実行しているWebブラウザー(または他のJavaScript実装)に依存することを意味します。
Samuel Edwin Ward

1
また、ブラウザーによって動作が異なる新しいDate(string)にも問題がありました。それは古いバージョンのIEでそれが壊れているという問題でさえありません、異なるブラウザは単に一貫していません。Date.parseまたはnew Date(string)を使用しないでください。
Hoffmann 2013年

195

JSインタープリターを書いた最近の経験の中で、ECMA / JSの日付の内部の仕組みを十分に取りました。だから、私はここに私の2セントを投入すると思います。うまくいけば、この情報を共有することで、ブラウザ間の日付の処理方法の違いについて疑問を持つ人を助けることができます。

入力側

すべての実装は、1970-01-01 UTC(GMTはUTCと同じ)からのミリ秒(ms)の数を表す64ビットの数値として内部的に日付値を格納します。この日付は、Javaなどの他の言語やUNIXなどのPOSIXシステムでも使用されるECMAScriptエポックです。エポックの後に発生する日付は正の数であり、それ以前の日付は負の数です。

次のコードは、現在のすべてのブラウザで同じ日付として解釈されますが、ローカルタイムゾーンオフセットが含まれます。

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

私のタイムゾーン(EST、つまり-05:00)では、結果は18000000です。これは、5時間で何ミリ秒なのかです(夏時間の月は4時間しかないため)。値はタイムゾーンによって異なります。この動作はECMA-262で指定されているため、すべてのブラウザーで同じように動作します。

主要なブラウザが日付として解析する入力文字列形式にはいくつかの違いがありますが、解析は主に実装に依存していますが、タイムゾーンと夏時間に関する限り、それらは基本的に同じように解釈されます。

ただし、ISO 8601形式は異なります。ECMAScript 2015(ed 6)で概要が説明されている2つの形式のうちの1つであり、すべての実装で同じ方法で解析する必要があります(もう1つはDate.prototype.toStringに指定された形式です)。

しかし、ISO 8601形式の文字列であっても、一部の実装ではそれが間違っています。ここではISO 8601形式の文字列を使用して私のマシン上でこの答えは、もともと1970年1月1日のために書かれたChromeとFirefoxのの比較出力(エポック)である必要があり、すべての実装に正確に同じ値に解析することは:

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
  • 最初のケースでは、「Z」指定子は、入力がUTC時間であるため、エポックからのオフセットではなく、結果が0であることを示します。
  • 2番目の場合、「-0500」指定子は入力がGMT-05:00であることを示し、両方のブラウザーが入力を-05:00タイムゾーンであると解釈します。これは、UTC値がエポックからオフセットされていることを意味します。つまり、日付の内部時刻値に18000000msが追加されます。
  • 指定子がない3番目のケースは、ホストシステムのローカルとして扱う必要があります。FFは入力を現地時間として正しく処理し、Chromeは入力をUTCとして処理するため、異なる時間値が生成されます。私にとっては、これにより、保存された値に5時間の差が生じ、問題が生じます。オフセットが異なる他のシステムでは、異なる結果が得られます。

この違いは2020年の時点で修正されていますが、ISO 8601形式の文字列を解析する場合、ブラウザー間に他の問題があります。

しかし、それはさらに悪化します。ECMA-262の奇妙な点は、ISO 8601の日付のみの形式(YYYY-MM-DD)をUTCとして解析する必要があるのに対し、ISO 8601はローカルとして解析する必要があることです。以下は、タイムゾーン指定子のないISOの長い形式と短い形式のFFからの出力です。

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

したがって、1つ目はタイムゾーンのないISO 8601の日付と時刻であるためローカルとして解析され、2つ目はISO 8601の日付のみであるためUTCとして解析されます。

したがって、元の質問に直接答えるに"YYYY-MM-DD"は、ECMA-262によってUTCとして解釈される必要があり、もう1つはローカルとして解釈されます。それが理由です:

これは同等の結果を生成しません:

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

これは:

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

一番下の行は、日付文字列を解析するためのこれです。ブラウザ間で安全に解析できる唯一のISO 8601文字列は、オフセット付きの長い形式(±HH:mmまたは "Z")です。これを行うと、現地時間とUTC時間の間を安全に行き来できます。

これはブラウザ間で動作します(IE9以降):

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

最新のブラウザは、頻繁に使用される「1/1/1970」(M / D / YYYY)や「1/1/1970 00:00:00 AM」(M / D / YYYY hh)など、他の入力形式を同等に扱います:mm:ss ap)フォーマット。次のすべての形式(最後を除く)は、すべてのブラウザーで現地時間入力として扱われます。このコードの出力は、私のタイムゾーンのすべてのブラウザーで同じです。タイムスタンプにオフセットが設定されているため、最後の1つはホストのタイムゾーンに関係なく-05:00として扱われます。

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

ただし、ECMA-262で指定された形式の解析でも一貫性がないため、組み込みのパーサーに依存せず、常にライブラリを使用して文字列を手動で解析し、形式をパーサーに提供することをお勧めします。

たとえば、moment.jsで次のように記述します。

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

出力側

出力側では、すべてのブラウザーがタイムゾーンを同じ方法で変換しますが、文字列形式の処理が異なります。次に、toString関数とそれらが出力するものを示します。注意してくださいtoUTCStringtoISOString私のマシン上の機能出力5:00を。また、タイムゾーン名は省略形である場合があり、実装によって異なる場合があります。

印刷前にUTCから現地時間に変換します

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

保存されているUTC時間を直接出力します

 - toUTCString
 - toISOString 

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

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

通常、文字列の入力にはISO形式を使用しません。その形式を使用することが私にとって有益な唯一の場合は、日付を文字列としてソートする必要がある場合です。ISO形式はそのまま並べ替え可能ですが、他の形式はそうではありません。ブラウザ間の互換性が必要な場合は、タイムゾーンを指定するか、互換性のある文字列形式を使用してください。

コードnew Date('12/4/2013').toString()は次の内部疑似変換を通過します。

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

この回答がお役に立てば幸いです。


3
まず、これは素晴らしい記事です。しかし、依存関係を指摘したかったのです。タイムゾーン指定子に関して、「指定子がない場合は現地時間の入力を想定する必要がある」と述べました。ありがたいことに、ECMA-262標準では、推定する必要がなくなります。それは述べ:Z「オフセット存在しない時間帯の値は『』」。したがって、タイムゾーンが指定されていない日付/時刻文字列は、現地時間ではなくUTCであると見なされます。もちろん、多くのJavaScriptと同様に、実装間での合意はほとんどないようです。
ダニエル

2
...最も頻繁に使用される「1/1/1970」および「1/1/1970 00:00:00 AM」形式を含みます。—最も頻繁に使用される場所?もちろん、それは私の国にはありません。
ulidtko 14

2
@ulidtko-すみません、私は米国にいます。うわー...あなたはキエフにいます。あなたとあなたの家族が安全を保ち、物事がすぐに安定することを願っています。自分を大事にして、すべてのことを頑張ってください。
drankin2112

ここだけのメモ。これはSafariブラウザ(つまり、iOSまたはOSX)では機能しないようです。それか私は他の問題が起こっています。
keyneom 2014

1
@Daniel —幸い、ECMAScriptの作成者は、日付と時刻の表現に欠落しているタイムゾーンに関するエラーを修正しました。タイムゾーンのない日付と時刻の文字列は、ホストのタイムゾーンオフセット(「ローカル」)を使用するようになりました。紛らわしいことに、ISO 8601の日付のみのフォームはUTCとして扱われます(仕様からは明確ではありません)。一方、ISO 8601はそれらをローカルとして扱い、すべてを修正するわけではありません。
RobG 2017年

70

狂気にはいくつかの方法があります。原則として、ブラウザーが日付をISO-8601として解釈できる場合、解釈されます。「2005-07-08」はこのキャンプに該当するため、UTCとして解析されます。「2005年7月8日」はできません。そのため、現地時間で解析されます。

JavaScriptと日付をご覧ください多くのための。


3
原則として、ブラウザーが日付をISO-8601として解釈できる場合、それは解釈されます。」はサポートされません。「2020-03-20 13:30:30」はISO 8601として扱われ、多くのブラウザではローカルですが、Safariでは無効な日付です。2004-W53-7や2020-092など、ほとんどのブラウザでサポートされていない多くのISO 8601形式があります。
RobG

7

別の解決策は、日付形式で連想配列を作成し、データを再フォーマットすることです。

このメソッドは、異常な方法でフォーマットされた日付に役立ちます。

例:

    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

moment.jsを使用して日付を解析します。

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

3番目の引数は、厳密な解析を決定します(2.3.0以降で利用可能)。これがなければ、moment.jsは誤った結果をもたらす可能性もあります。


4

http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.htmlによると、「yyyy / mm / dd」という形式で通常の問題が解決されます。「可能な限り、日付文字列は常に「YYYY / MM / DD」に固執してください。これは、普遍的にサポートされており、明確です。この形式では、常にローカルです。」テストを設定しました:http : //jsfiddle.net/jlanus/ND2Qg/432/ この形式:+ ymd順序と4桁の年を使用して日と月の順序のあいまいさを回避します+ UTCとローカルの問題を回避しませんスラッシュ+ danvk(dygraphsの人)を使用してISO形式に準拠すると、この形式はすべてのブラウザーで有効であると述べています。


著者の回答を確認することもできます
ブラッドコッホ

日付ピッカーパーサーを使用するjQueryを使用する場合、jsFiddleの例を使用したソリューションは十分に機能します。私の場合、問題はjqGridにありますが、parseDateメソッドがあることがわかりました。とにかく、この例は私にアイデアを与えてくれたので、+ 1をしてくれてありがとう。
Vasil Popov 2013年

2
dygraphに関するその記事は間違っており、ページの最初の例は、ハイフンの代わりにスラッシュを使用することが本当に悪いアドバイスである理由を明確に示しています。記事の執筆時点では、「2012/03/13」を使用すると、ブラウザでUTCではなくローカルの日付として解析されていました。ECMAScript仕様では、 "YYYY-MM-DD"(ISO8601)を使用するための明示的なサポートが定義されているため、常にハイフンを使用してください。このコメントを書いている時点で、スラッシュをUTCとして扱うようにChromeにパッチが適用されていることに注意してください。
Lachlan Hunt

4

一方でCMSが正しい解析メソッドに文字列を渡すと、一般的に安全ではないことを、新しいECMA-262第5版セクション15.9.4.2で(ES5別名)仕様は、ことを示唆しているDate.parse()実際には、ISO形式の日付を処理する必要があります。古い仕様ではそのような主張はありませんでした。もちろん、古いブラウザと一部の現在のブラウザは、このES5機能をまだ提供していません。

2番目の例は間違っていません。これは、に示されているようにDate.prototype.toISOString()、UTCで指定された日付ですが、ローカルタイムゾーンで表されます。


1
また、ECMAScript 2015で日付文字列の解析が再び変更され、「2005-07-08」はUTCではなくローカルになるようになりました。ところで、ES5は2011年6月まで標準ではありませんでした(現在ECMAScript 2015は標準です)。;-)
RobG 2015

1
混乱を避けるために、TC39は10月(前回の投稿から1か月後)に、「2005-07-08」をUTCに、「 "2005-07-08T00:00:00"をローカルに」に決定しました。どちらもISO 8601です。準拠フォーマット、どちらもタイムゾーンなしですが、異なる方法で処理されます。図を参照してください
RobG '22

2

この軽量の日付解析ライブラリは、同様の問題をすべて解決するはずです。拡張が非常に簡単なので、ライブラリが好きです。i18nにすることも可能です(それほど単純ではありませんが、それほど難しくありません)。

解析例:

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

そして、文字列にフォーマットし直します(どちらの場合もまったく同じ結果になることに気づくでしょう):

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

@ drankin2112で詳しく説明されているnicelのように、クロスブラウザセーフな方法でdatetime-stringを変換する短い柔軟なスニペットを次に示します。

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

ブラウザは、次のものと同じタイムスタンプ結果を提供する必要がありますDate.parse

(new Date(tstring)).getTime()

すでにJS形式の日付もキャッチするには、正規表現にTを追加することをお勧めします:inputTimestamp.split(/ [T \ /:-] / g)
andig

文字列をコンポーネント部分に分割する場合、最も信頼できる次のステップは、それらの部分をDateコンストラクターの引数として使用することです。パーサーに渡す別の文字列を作成すると、手順1に戻ります。「2014-04-29 13:00:15」はローカルとして解析され、コードはそれをUTCとして再フォーマットします。:-(
RobG 2017

2

どちらも正しいですが、2つの異なるタイムゾーンを持つ日付として解釈されています。だからあなたはリンゴとオレンジを比較しました:

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

Date.parse()文字列引数で自動的に使用されるため、呼び出しを削除しました。また、ISO8601形式を使用して日付を比較したので、ローカルの日付とUTC日付の日付を視覚的に比較できます。時間は7時間離れています。これはタイムゾーンの違いであり、テストで2つの異なる日付が表示された理由です。

同じローカル/ UTC日付を作成する別の方法は次のとおりです。

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"

しかし、私はまだシンプルで強力なMoment.jsを強くお勧めします

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

CMSから受け入れられた答えは正しいです、私はちょうどいくつかの機能を追加しました:

  • 入力スペースをトリムおよびクリーンする
  • スラッシュ、ダッシュ、コロン、スペースを解析する
  • デフォルトの日時があります

// 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
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.