Help us understand the problem. What is going on with this article?

JavaScript の Date は罠が多すぎる

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"

日付を維持したい場合、タイムゾーンの情報を保持したい場合は、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 (のサブセット) をパースできるよう定めている。

labocho
普通の Web プログラマ
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした