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.parse や new 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 (のサブセット) をパースできるよう定めている。