JavaScriptのgetMonthメソッドはインデックス値を返す

2021-10-14 (Thu)
DateオブジェクトのgetMonthメソッドの注意点

Table of Contents

はじめに

今回はJavaScriptのDateオブジェクトでのハマりやすい罠に関する記事です。
正直誰でもハマったことがあるとは思いますが、個人的な備忘録として残しておきます。
なぜこのような仕様になっているのかを理解することで、今後は間違いにくくなると思います。

getMonthメソッドの罠

今回罠にハマったのはJavaScriptのDategetMonthメソッドです。
DateオブジェクトはJavaScriptで時刻を扱うときに利用するものです。
例えば、以下のように記述することで現在時刻のDateオブジェクトを作れます。

JavaScript
const now = new Date();

console.log(now);
// Thu Oct 14 2021 22:18:27 GMT+0900 (日本標準時)

Dateオブジェクトには年や月、日などを取り出すためのメソッドが用意されています。
例えば、西暦を取得するためのgetFullYearや日付を取得するgetDate、時・分を取得するためのgetHoursgetMinutesなどがあります。
使えるメソッドについてはこちらのドキュメントで確認できます。

JavaScript
console.log(now.getFullYear());
// 2021

console.log(now.getDate());
// 14

console.log(now.getHours());
// 22

ちゃんと取得できているのがわかるかと思います。

では、ここからが本題です。
Dateオブジェクトから「月」を取り出すにはどうすれば良いでしょうか?
先ほどのドキュメントを確認すると、getMonthというメソッドがあることがわかります。
使ってみましょう。

JavaScript
console.log(now.getMonth());
// 9

あれ?
今は10月なのに、"9"という数値が返ってきました。
これが私が引っかかった罠です。
実は全く気づかず、つい先日まで本ブログの記事は全て1ヶ月分ズレた日付が表示されていましたw

ちなみに私が普段よく使っているPythonの日付時刻ライブラリであるdatetimeでは、月は1~12で表現されています。
(そのため、この仕様は完全に油断していました…w)

なぜこの仕様なのか?

ドキュメントを確認すると、以下のように0を基準とした値とちゃんと書いてあります。

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

ちなみに、日付を取得するgetDateメソッドは1-31の数値を返すので、getMonthメソッドの挙動とは異なります。
なぜこのような挙動になっているのか調べてみたところ、(明確な理由はわかりませんでしたが)以下の説が存在することがわかりました。

  • 欧米の言語では、月は数値ではなく単語で表現するため
  • Cなど古くから存在する言語のライブラリの仕様を踏襲したため

この1つ目の理由が根幹にある気がしました。
日本では、1月、2月、…と数字を使って月を表しますが、英語ではJanuary, February, …と単語で表します。
そのため、以下のように「月」を表す配列を用意し、インデックスとしてgetMonthで取得した数値を渡すだけで簡単にその月の単語を取得できます。

JavaScript
const now = new Date();

// 英語の月名配列
const eng = [
  'January',
  'February',
  'March',
  'April',
  'May',
  'June',
  'July',
  'August',
  'September',
  'October',
  'November',
  'December',
];

console.log(eng[now.getMonth()]);
// October

かなり自然にコードが書けますね。
配列を変えれば他の言語などへの対応なども簡単にできます。

JavaScript
// イタリア語の月名配列
const italy = [
  'Gennaio',
  'Febbraio',
  'Marzo',
  'Aprile',
  'Maggio',
  'Giugno',
  'Luglio',
  'Agosto',
  'Settembre',
  'Ottobre',
  'Novembre',
  'Dicembre',
];

console.log(italy[now.getMonth()]);
// Ottobre

要するに、getMonthメソッドは月名の配列のインデックス値を返してくれると考えると、0から始まることへの違和感はなくなるかと思います。
また、日付に関しては英語などでも数値で表現します (14th October, 2021のように)。
そのため、getDateメソッドはインデックス値ではなく、そのまま日付の数値を返してくれるのかな、と思いました。
そう考えると、日本人が疑問に思うこの仕様も、国際的にはあまり疑問を感じないのかもしれません。

そういえば、日本も昔は異なる月の呼び方をしていました。
(和風月名というみたいですね。)

JavaScript
const jpnOld = ['睦月', '如月', '弥生', '卯月', '皐月', '水無月', '文月', '葉月', '長月', '神無月', '霜月', '師走'];

console.log(jpnOld[now.getMonth()]);
// 神無月

和風月名を使っていた時代の人たちがJavaScriptを触ったら (謎の世界観)、getMonthメソッドの仕様の罠にハマることもなかったかもしれないですね。

まとめ

今回は、JavaScriptのDateオブジェクトにおけるgetMonthメソッドに潜む罠 (勝手にそう呼んでいるだけ…)について説明しました。
月名配列に入力するためのインデックス値を返してくれると考えると、自然に受け入れられる仕様だと感じられるのではないでしょうか?
getMonthメソッドを使うときは、この話を思い出していただけると、注意してコーディングできるかもしれません。

Author Profile
liebe-magi

りーべ / liebe-magi

ものづくりが大好きな自称フルスタック(?)エンジニア。大学・大学院でコンピュータサイエンスを専攻し、現在は某企業の研究所所属。専門は組み合わせ最適化問題や機械学習など。主に使用している言語はPython、JavaScript (TypeScript)、Rust、Go。最近は競技プログラミングに興味を持ち、AtCoderのコンテスト (ABC) に毎週参加中 (現在緑)。趣味はマジック、漫画・アニメ、ゲーム(電源・電源問わず)。