JavaScript

JavaScript の Date は罠が多すぎる

More than 3 years have passed since last update.

JavaScript で日付・時間を扱っていて、次から次へと罠にはまったので、あとから来る人のために書き留めておく。


Date.parse が返すのは Date でなく整数

Date.parse は、世界協定時 1970 年 1 月 1 日 00:00:00 からのミリ秒を返す。

Date を得るには new Date に渡す。

new Date に直接文字列渡しても同じ挙動なので、こちらのが簡潔。

msec = Date.parse("Thu, 06 Sep 2012 00:00:00 +0900"); // 1346857200000

date = new Date(msec); // Date
date = new Date("Thu, 06 Sep 2012 00:00:00 +0900"); // Date


年を得るのに getYear は使わない

Date.prototype.getYear は 1900 年からの年数で非推奨。

Date.prototype.getFullYear を使う。

date = new Date("Thu, 06 Sep 2012 00:00:00 +0900")

date.getYear(); // 112 (2012 - 1900)
date.getFullYear(); // 2012


月は 0 から始まる

new Date の第 2 引数でも、Date.prototype.getMonth でも、月は 0-11で表す。

new Date(2012, 8, 6); // Thu, Sep 06 2012 00:00:00 GMT+0900 (JST)

new Date("Thu, 06 Sep 2012 00:00:00 +0900").getMonth(); // 8


日を得るのは getDay ではない

Date.prototype.getDay は 0 を日曜とする曜日を返す。

日にちは Date.prototype.getDate

月とは違いこちらは 1-31。

date = new Date("Thu, 06 Sep 2012 00:00:00 +0900");

date.getDay(); // 4 (木曜日)
date.getDate(); // 6 (6日)


getTimezoneOffset の符号は普通の表記と逆

Date.prototype.getTimezoneOffset は UTC からの時差を分で返すが、通常タイムゾーンを示す文字列表現とは符号が逆。

date = new Date("Thu, 06 Sep 2012 00:00:00 +0900");

date.getTimezoneOffset(); // -540 (540分=9時間で +0900 のこと)


比較するときは getTime 使う

同じ日時を示す Date でもオブジェクトが異なれば == や === は false になる。

s = "Thu, 06 Sep 2012 00:00:00 +0900"

new Date(s) == new Date(s) // false
new Date(s) === new Date(s) // false

Date.prototype.getTime で 世界協定時 1970 年 1 月 1 日 00:00:00 からの経過ミリ秒が得られるのでこれで比較する。

s = "Thu, 06 Sep 2012 00:00:00 +0900"

new Date(s).getTime() // 1346857200000
new Date(s).getTime() == new Date(s).getTime() // true
new Date(s).getTime() === new Date(s).getTime() // true

ちなみに Date 同士の比較でも <> は期待通りに動作するが、ややこしいのでおすすめしない。

(減算したり単項演算子 + を使って数値にキャストして比較する方法もあるが、意図がわかりにくい上に遅いので使う理由はない)


文字列表現は toISOString か toJSON で取得する

ECMAScript 3 で定義されている、文字列表現を得るメソッドには、下記のものがある (括弧内は EMCAScript 3 仕様書 (PDF) の項番号)。


  • toString (15.9.5.2)

  • toDateString (15.9.5.3)

  • toTimeString (15.9.5.4)

  • toLocaleString (15.9.5.5)

  • toLocaleDateString (15.9.5.6)

  • toLocaleTimeString (15.9.5.7)

が、いずれも "The contents of the string are implementation-dependent" と書かれており、形式は実装依存。

試してみたら違うのは IE だけだったけど、規格がないので責めてはいけない。

date = new Date("Thu, 06 Sep 2012 00:00:00 +0900");

date.toString() // Chrome21: "Thu Sep 06 2012 00:00:00 GMT+0900 (JST)"
date.toString() // Firefox14: "Thu Sep 06 2012 00:00:00 GMT+0900 (JST)"
date.toString() // Safari: "Thu Sep 06 2012 00:00:00 GMT+0900 (JST)"
date.toString() // IE8: "Thu Sep 6 00:00:00 UTC+0900 2012"

ECMAScript 5 では文字列表現をISO8601 のサブセットに規定しており toISOString で得られる。

date = new Date("Thu, 06 Sep 2012 00:00:00 +0900");

date.toISOString(); // "2012-09-05T15:00:00.000Z"

JSON.stringify から呼ばれる toJSON も toISOString を使うので、JSON.stringify したときも ISO8601 になる。

date = new Date("Thu, 06 Sep 2012 00:00:00 +0900");

JSON.stringify(date); // ""2012-09-05T15:00:00.000Z""

また、Date.parsenew Date でも、ISO8601 (のサブセット) をパースできることを定めている。

new Date("2012-09-05T15:00:00.000Z") // Thu Sep 06 2012 00:00:00 GMT+0900 (JST)


JSON にすると日付が変わることがある

上で述べたように JSON.stringify したときの文字列表現は ISO8601 になるが、タイムゾーンを無視して常に UTC になってしまう

これにより、タイムゾーンと時刻によっては日付が変わる。

// 日本時間で 6 日 0 時は UTC では 5 日 15 時

date = new Date("2012-09-06T00:00:00+0900");
JSON.stringify(date) // "2012-09-05T15:00:00.000Z"

Date.prototype.toJSON を自分で書き換えてタイムゾーンを含む文字列を返すようにすれば回避可能だが、規格から外れてしまうため、JSON.stringify する前に文字列にしてしまうほうが安全。


日付をパースした場合と数値で指定した場合で時刻が一致しない

new Date や Date.parse で時刻を含まない日付文字列をパースすると、UTC 00:00:00 になる。

// UTC 0 時 = JST 午前 9 時

new Date("2014-11-10") // Mon Nov 10 2014 09:00:00 GMT+0900 (JST)

一方で new Date(2014, 10, 10) などとしたときは地方時 00:00:00 となり、これと時刻が一致しない。

new Date(2014, 10, 10) // Mon Nov 10 2014 00:00:00 GMT+0900 (JST)

前者の挙動は、日付文字列でタイムゾーン文字列がない場合、UTC として扱うという ECMAScript 5 の仕様に基づく。


The value of an absent time zone offset is “Z”.

15.9.1.15 Date Time String Format


ただし、ECMAScript 6 draft (October 14, 2014) では、これを地方時にするとの記述がある。


If the time zone offset is absent, the date-time is interpreted as a local time.

20.3.1.15 Date Time String Format



ISO8601 は IE8 ではパースできない

Date.parse がパースできる形式は ECMAScript 3 では定められていないため実装依存。

試してみたら、主要ブラウザでは RFC2822 なら OK、ISO8601 は IE8 ではだめ (IE9 は OK)。

Date.parse("2012-01-01 00:00:00"); // IE8 だと NaN

上で述べたように ECMAScript 5 では ISO8601 (のサブセット) をパースできるよう定めている。