【js】落とし穴にハマるな!Dateクラスで日付を操作する技 5選

javascriptの標準ライブラリで悪名高いDateクラス。
通常はmoment.jsなどのラッパーライブラリを使って安全に作りますが、ちょっとしたスクリプトくらいだとライブラリは使わずにスクラッチで書きます。
そんな時に使える日付操作周りのTipsを列挙して解説していきます。 日付操作の解説の中でいくつか落とし穴があるので、それについても解説します。

紹介する日付操作一覧

  • 今日の日付を取得する
  • 明日の日付を取得する
  • 今週の火曜日を取得する
  • 先月末を取得する
  • 今月末を取得する

今日の日付を取得する

Dateは日付と時刻を両方含んだクラスです。
そこから時刻を削り、日付のみを抽出します。

new Date(new Date().toLocaleDateString())

解説

Dateのコンストラク

まずはDateのコンストラクタから解説します。 コンストラクタにはざっくり2種類あります。それは引数ありとなしです。

引数なしの場合、現在日時が取得できる

現在日時はDateのコンストラクタを引数なしで呼び出せば生成できます。

var now = new Date();

引数ありの場合、指定した日時が取得できる

引数ありの場合は、指定した日付が取得できます。指定の仕方は様々ありますが、new Date('2020/3/24')のように日付をyyyy/mm/ddの形式で指定すると日付のみで生成できます。

以上より、今回目的とする「今日の日付」を取得すためには、Dateの引数なしコンストラクタで現在日時を取得し、それをyyyy/mm/ddの形式の文字列に変換して、Dateの引数ありコンストラクタに渡せばOKです。

日時からyyyy/mm/ddを取り出す

DateクラスにはtoLocaleDateString()というメソッドがあります。 このメソッドを呼び出すとyyyy/mm/ddの形式で返ってきます。

まとめ

以上をまとめると下記のようになります。

var now = new Date();// 現在日時のDate生成
var todayStr = now.toLocaleDateString(); // yyyy/mm/ddの形式で取得
var today = new Date(todayStr); // 日付だけのDate生成

これで今日の日付が取得できます。 これを1行で書いたのが冒頭の

new Date(new Date().toLocaleDateString())

です。

落とし穴: 日付をハイフン区切りにすると死ぬ

Date生成で日付をスラッシュ区切りではなくハイフン区切りで書くことがあります。たとえばnew Date('2020-03-24')のように。
これでDateを生成すると結果は「2020年3月24日の午前9時」になります。「9時ってなんだよ!イギリスかよ!」て思いますね。。
一方new Date('2020-3-24')のように月や日付をゼロ埋めしない場合は日本時間で「2020年3月24日の午前0時」になります。
つまりゼロ埋めの有無で結果が変わるんです。
ちなみに例えば2020/10/10のように月も日も2桁の値を指定されたら、これがゼロ埋めされてるか判別できないのですが、この場合は「ゼロ埋めあり」の判定になり、イギリス時間になります。 なのでnew Date('2020/10/9')は日本時間ですが、翌日のnew Date('2020/10/10')はイギリス時間になります。
怖すぎる。。
区切り文字周辺の挙動は怪しいの以下にまとめておきます。

new Date()の引数 結果
2020/1/1 日本時間
2020/01/01 日本時間
2020-1-1 日本時間
2020-01-01 イギリス時間
2020-01-01 00:00 日本時間

(ゼロ埋めアリでも時刻を指定すると日本時間になるのか...)

とりあえずスラッシュ区切りにしておけば安心だ!

明日の日付を取得する

今日の日付取得を応用すれば明日の日付が取得できます。

var d = new Date(new Date().toLocaleDateString())
d.setDate(d.getDate() + 1)

1行目は先ほどの「今日の日付」です。 2行目を解説します。

解説

date.getDate()

2行目の括弧内にあるd.getDate()では今日の日が取得できます。
今日が3/24ならば24が取得できます。
これに1を加えて、日付を更新するメソッドsetDate()に渡します。

date.setDate()

最後にd.setDate()の部分です。setDate()は日付を更新します。
引数に月の末日よりも大きな値が来た場合は年や月をいい感じに更新してくれます。
例えば12月32日を指定すると、翌年の1月1日にしてくれます。

var d = new Date('2019/12/31') // 12月
d.setDate(32)` // => 2020/1/1

まとめ

冒頭のプログラムは、今日の日付が入っている変数dから、getDate()で日を取り出して1を加え、setDate()に渡すことで、明日の日付を取得しています。

今週の火曜日を取得する

次は先ほどのsetDate()の応用編です。 今週の特定の曜日を取得します。今回は例として火曜日を取得します。

取得例:

  • 現在が2020/3/25(水)ならば2020/3/24(火)が取れる
  • 現在が2020/3/29(日)ならば2020/3/31(火)が取れる
var d = new Date(new Date().toLocaleDateString())
d.setDate(d.getDate() + 2 - d.getDay())

解説

1行目の日付取得、2行目のsetDateによる日の更新は今まで解説した通りなので省略します。 ここでは2行目のsetDateの中身を解説します。

date.getDay()

date.getDay()で曜日が数値で取得できます。 数値は日曜日なら0、月曜日なら1、...土曜日なら6です。

火曜日までの日数

括弧内の2 - d.getDay()は何をしているのか? まず2は火曜日という意味です。 そこから今日の曜日を引くと火曜日までの日数を計算しています。 例えば今日が日曜日(0)ならば 2 - 0 で2日、今日が金曜日ならば 2 - 5で-3日です。 これを現在の日に加える(または引く)と火曜日の日付が取得できます。

まとめ

getDay()で曜日を数値で取得し、特定の曜日から日数を計算することにより、今週の特定の曜日が取得できました。 プログラムの2の部分を他の数値に変えると、別の曜日を取得できます。

先月末を取得する

var d = new Date(new Date().toLocaleDateString())
d.setDate(0)

解説

「明日の日付」の解説でd.setDate()に月末よりも大きな値を入れた場合にいい感じにしてくれる話を書きましたが、0やマイナス値のような月初よりも小さい値を入れた場合もいい感じにしてくれます。
例えば2020年1月0日は2019年12月31日になります。
つまり0日は先月末の意味になります。
なので現在の日付に対してsetDate(0)をすると先月の末日が取得できます。

今月末を取得する

先月末を応用して、「来月の前の月の月末」を取得すれば今月末になります。

var d = new Date(new Date().toLocaleDateString())
d.setDate(1)
d.setMonth(d.getMonth() + 1)
d.setDate(0)

解説

2行目のsetDate(1)は「ミソ」なのでちょっと飛ばして、3行目から解説します。

date.getMonth()

date.getMonth()で月を数値で取得します。
数値は1月は0、2月は1、...、12月は11になります。0から始まるところが特徴的です。

date.setMonth()

date.getMonth()で月を指定できます。-1や13のようなありえない月を入れてもいい感じにしてくれます。 なので

d.setMonth(d.getMonth() + 1)

の部分では括弧内で今月を取得し、それに1を加えて、setMonth()に渡すことで「来月」にしています。 この来月に対して「先月末」を意味するsetDate(0)をすることにより、「今月末」を取得できます。

落とし穴: getMonth() + 1が2ヶ月後になる

実は上記のやり方には問題があります。
たとえば現在が2020/1/31の場合、今月に1を加えると、2020/2/31となり、2月に31日は無いので、結果は2020/3/2になります。この3/2対してsetDate(0)をすると2月末になり、期待した1月末になりません。
つまり、現在の日が29, 30, 31のような、月によってあったりなかったりする日付の場合にバグになります。 この問題を解決するためには、日を安全な1日に変更しておけばOKです。それが2行目のsetDate(1)です。

まとめ

現在の日を今月の初日に変更し、来月を取得し、そこから先月末を取得することにより、今月末を取得できます。

全体まとめ

今回はDateから様々な日付を取得する方法を列挙しつつ、いくつかDateのハマりポイントも紹介しました。
jsのDateは噛めば噛むほどツラ味がでるな...ライブラリ使おうw
それではまたー。