Roll With IT

tamakiのIT日記

DateオブジェクトのgetMonth()メソッドは、JSTを基準とした値を返す

f:id:shirotamaki:20211031221627p:plain
出典: いらすとや

はじめに

JavaScriptでは、Dateオブジェクトから、指定したその月の最終日(月末日)を取り出す際、getMonth()メソッド を利用します。

上記の一文だけ読むとそんなに難しいことではない気がするのですが、実際にやってみて、JavaScript独特のDateオブジェクトの挙動に翻弄され、ハマりまくってしまいました。

JavaScript の Dateは、初心者がハマりがちな罠が多いそうです。

何だか面倒な挙動が多く「クセが強い!」Dateオブジェクト。

そんな同じ状況でハマってしまう方の助けになればと思いブログに書き留めておきます。

前提知識

UTCGMTJSTの違い

まず、前提としてDateオブジェクトはUTC協定世界時)を基準としています。

UTCの前は、GMTが世界の標準時でした。

  • GMTの特徴
    • 英国のグリニッジ天文台(経緯0度)での地方平均時(平均太陽が南中する時を正午とする)のことで、天文観測(地球の自転)によって決められる時刻
    • UTCとほとんど同じだが、現在はUTCのほうがより正確であるため、そちらを世界標準として採用している
    • グリニッジ標準時 - Wikipedia

それから、UTCの時刻へ9時間プラスした時刻をJSTと呼びます。

ISO 8160

それから、日付と時刻の表記に関する国際規格に沿って、形式や表記が定められています。

以下、返り値として返される、2021-10-31T11:31:04.554Zや、2021-10-30T15:00:00.000Zという表記も、ISO 8160に準じて定められています。

ISO 8601 - Wikipedia

ハマってしまったこと

Dateオブジェクト

developer.mozilla.org

まずは、Dateオブジェクトを作ってみます。 Date.now()メソッドを使い、現在時刻(UTC)を生成することができます。 ここでは、「UTC」であることがポイントです。

> now = new Date();
2021-10-31T11:31:04.554Z

ここから、その月の最終日を取得するために、以下の引数を渡してあげます。 第三引数に0を渡すと、その月の最終日が返ってくるはずですが…

date = new Date(年, 月, 0);

date = new Date(2021, 10, 0);
2021-10-30T15:00:00.000Z

あれ? 10月30日で最終日ではない…

10月は、31日まで存在しますが、10月30日となっています。

UTCだから、おかしなことになっているのではないかと考え、JSTに変換するメソッドがないか調べてみました。

toLocaleString()メソッド

developer.mozilla.org

ありました。

このメソッドを使うことでJSTへ変換できるようです。

.toLocaleString({ timeZone: 'Asia/Tokyo' })

> now = new Date().toLocaleString({ timeZone: 'Asia/Tokyo' });
'2021/10/31 21:10:38'

出来ました! 現在の日本時刻(21:10)と同じです。

このオブジェクトを使って、今回やりたかったこと(その月の最終日を取得する)試してみます。

まずは、UTCでその月の最終日を取得します。

> const date = new Date(2021, 10, 0);
undefined
> date
2021-10-30T15:00:00.000Z

ここから、変数dateに対してメソッドを実行してみます。 結果、無事に10月31日と表示されました。

> date.toLocaleString({ timeZone: 'Asia/Tokyo' });
'2021/10/31 0:00:00'

toLocaleStringメソッド は、文字通り String(文字列)に修正してしまうものなので注意

ここで本題のgetMonth()メソッドを使い、指定された日時の「月」だけ取得を試みてみます。

> const date = new Date(2021, 10, 0);
undefined
> const endDate = date.toLocaleString({ timeZone: 'Asia/Tokyo' });
undefined
> endMonth = endDate.getMonth();
Uncaught TypeError: endDate.getMonth is not a function

ん? Uncaught TypeError: endDate.getMonth is not a function、値を関数として呼び出そうとしたが、その値が実際には関数ではなかった場合に発生するエラーが返ってきました。

これは小見出しにもあるように、「toLocaleStringメソッド は、文字通り String(文字列)に修正してしまうもの」なので、変数endDateに代入されている値は、Stringなので、レシーバとしては適切ではないようです。

getMonth()メソッドに対しては、Detaオブジェクトを利用する必要があるようです。

以上、私がハマった経緯になります。次に解決方法をまとめます。

結論

ここからは、ハマったポイントを抜け出した結論になります。

正直、結論は「え?そんなことだったの…」と、拍子抜けの内容です。 しかし、上記のDateオブジェクトの挙動のクセを体感したからこその拍子抜けなので、今回は遠回りでしたがとてもよい経験となりました。

DateオブジェクトのgetMonth()メソッドは、JSTを基準とした値を返す

MDNには、以下のように書いていました。

注: Date オブジェクトの中心となる時間値は UTC ですが、日付と時刻、またはその一部を取得する基本的なメソッドは、すべて地方時 (ホストシステムなど) のタイムゾーンとオフセットで動作することを覚えておくことが重要です。

要は、DateオブジェクトのgetMonth()メソッドJSTとして返事をくれるようです!!

一点、注意点としては、0-11の数字で扱っているので、正確な月に修正するには +1 が必要です。

変数dateへ、10月の最終日(31日)のオブジェクトを生成して代入します。

確認してみると、UTCでは、2021-10-30T15:00:00.000Z と表記されています。

> const date = new Date(2021, 10, 0);
undefined
> date
2021-10-30T15:00:00.000Z

では、ここで「getMonth()メソッドJSTとして返事をくれる」が正しいか試してみます。

> date.getMonth();
9

「9」!

無事に9が表示されました!!!

上述しましたが、0-11の数字で扱っているので、9は、10月のことになります。

ちゃんとJSTとして変換して返してくれています。

おまけ

諸々が解決したあと、改めてMDNを読み直してみました。

すると、以下のように「地方時に基づき」と書いてありました…汗

地方時 == 日本の時刻 ですね。

getMonth() メソッドは、地方時に基づき、指定された日付の「月」を表す 0 を基点とした値 (すなわち 0 が年の最初の月を示す) を返します。

developer.mozilla.org

何だか今日は、JavaScriptのトリックに翻弄された一日でした…(JavaScriptにそんなつもりは無いと思いますが…)

引き続きドキュメントをしっかり読み解き、ひとつひとつやっていきたいと思います。

ハッピーハロウィーーン!!