完全理解!JavaScript `Date`オブジェクトと`new Date()`の使い方


完全理解!JavaScript Dateオブジェクトとnew Date()の使い方

JavaScriptで日付や時間を扱う際、最も基本的なツールとなるのがDateオブジェクトです。現在の日時を取得したり、特定の日時を表したり、あるいは日付や時間の計算を行ったりと、Webアプリケーション開発において非常に頻繁に利用されます。しかし、Dateオブジェクトはその設計や挙動にいくつか注意すべき点があり、特にタイムゾーンや文字列のパースに関しては混乱しやすい部分です。

この記事では、JavaScriptのDateオブジェクトと、新しいDateインスタンスを作成するためのコンストラクタであるnew Date()の使い方について、その基本から応用、そして多くの開発者が陥りやすい注意点や落とし穴に至るまで、徹底的に解説します。約5000語という分量で、可能な限り詳細に掘り下げていきます。

はじめに

JavaScriptのDateオブジェクトは、特定の時点を表現するために使用されます。この「特定の時点」は、UTC(協定世界時)の1970年1月1日午前0時0分0秒からの経過ミリ秒数(エポックタイム、またはUnixタイム)として内部的に保持されます。

なぜ日付と時間を扱うことが重要なのでしょうか?
– ユーザーに現在の日時を表示する。
– イベントのスケジュール管理(開始日時、終了日時)。
– ログのタイムスタンプ。
– データの有効期限チェック。
– 時系列データの処理。
– 異なるタイムゾーン間での日時変換。

これらの様々なシナリオに対応するため、Dateオブジェクトとそのメソッドを正しく理解することが不可欠です。

new Date()の様々な呼び出し方

Dateオブジェクトのインスタンスを作成するには、new Date()コンストラクタを使用します。このコンストラクタには、いくつかの異なる引数の指定方法があり、それぞれ異なる意味を持ちます。

  1. 引数なし: new Date()
  2. 数値(ミリ秒エポックタイム)を指定: new Date(milliseconds)
  3. 日時文字列を指定: new Date(dateString)
  4. 複数の数値引数(年, 月, …)を指定: new Date(year, month, day, hours, minutes, seconds, milliseconds)

これらの各形式について詳しく見ていきましょう。

1. 引数なし: 現在の日時を取得

最も単純な形式です。引数を何も指定せずにnew Date()を呼び出すと、それはスクリプトが実行された時点でのローカルタイムの現在日時を表すDateオブジェクトを作成します。

javascript
const now = new Date();
console.log(now); // 例: 2023-10-27T05:30:00.123Z のような出力 (実際には環境依存)

console.log()の出力形式はJavaScriptエンジンや環境によって異なりますが、一般的にはISO 8601形式やそれに近い形式で表示されます。このDateオブジェクトは、後述する様々なメソッドを使って、年、月、日、時間などの個別の要素を取り出したり、変換したりできます。

2. 数値(ミリ秒エポックタイム)を指定

new Date()に一つの数値引数を指定すると、それはUTCの1970年1月1日午前0時0分0秒(エポック)からの経過ミリ秒数として解釈されます。このミリ秒数を指定することで、過去や未来の特定の日時を表すDateオブジェクトを作成できます。

“`javascript
// エポックから1000ミリ秒(1秒)後の日時
const epochPlus1Second = new Date(1000);
console.log(epochPlus1Second); // 例: 1970-01-01T00:00:01.000Z (UTCでの時刻)

// 特定のエポックタイム (例: 2000年1月1日午前0時0分0秒 UTC)
const specificEpoch = 946684800000; // 2000-01-01T00:00:00.000Z
const dateFromEpoch = new Date(specificEpoch);
console.log(dateFromEpoch); // 例: 2000-01-01T00:00:00.000Z
“`

この形式は、例えばAPIからエポックタイムとして日時情報を受け取った場合などに便利です。内部的には、すべてのDateオブジェクトはこのエポックからのミリ秒数として日時を保持しているため、これはDateオブジェクトの最も基本的な表現形式と言えます。

3. 日時文字列を指定

new Date()に日時を表す文字列を指定すると、JavaScriptエンジンはその文字列をパース(解釈)して対応する日時を持つDateオブジェクトを作成しようとします。

“`javascript
const dateString1 = new Date(‘2023-10-27T10:00:00Z’); // ISO 8601形式 (UTC)
console.log(dateString1);

const dateString2 = new Date(‘October 27, 2023 10:00:00’); // RFC 2822形式に準拠
console.log(dateString2);

const dateString3 = new Date(‘2023-10-27’); // ISO 8601形式 (日付のみ)
console.log(dateString3); // パース結果はタイムゾーンに依存する場合あり
“`

重要: 文字列パースは、Dateオブジェクトの最も注意が必要な部分の一つです。JavaScriptの仕様では、日時文字列のパースに関して具体的な形式の標準がいくつか規定されていますが、すべての形式がすべてのJavaScriptエンジンで一貫して正確にパースされる保証はありません。特に、YYYY/MM/DDMM/DD/YYYY のような形式は、どちらの形式として解釈されるかが実装依存となるため、使用を避けるべきです。

最も推奨されるのは、ISO 8601形式 (YYYY-MM-DDTHH:mm:ss.sssZYYYY-MM-DD など) です。この形式は国際標準であり、多くの環境で一貫してパースされます。タイムゾーン情報を含める場合は、末尾に Z(UTC)や +HH:mm/-HH:mm を付けることができます。タイムゾーン情報がない場合、その文字列がUTCとして扱われるかローカルタイムとして扱われるかは、形式や環境によって異なる可能性があります。

4. 複数の数値引数(年, 月, 日, 時, 分, 秒, ミリ秒)を指定

new Date()に複数の数値を引数として指定することで、特定の日時を直接指定してDateオブジェクトを作成できます。この形式で指定された日時は、ローカルタイムとして解釈されます。

引数は以下の順番で指定します。
new Date(year, month, day, hours, minutes, seconds, milliseconds)

  • year: 年 (例: 2023)。必ず4桁で指定してください。 2桁で指定すると予期しない年になる可能性があります(例: 99が1999年ではなく2099年になるなど)。
  • month: 月。0から始まるインデックスで指定します。つまり、1月は0、2月は1、…、12月は11です。これは非常に間違いやすいポイントなので注意が必要です。
  • day: 日。1から始まる数値です。省略した場合、1として扱われます。
  • hours: 時。0から23までの数値です。省略した場合、0として扱われます。
  • minutes: 分。0から59までの数値です。省略した場合、0として扱われます。
  • seconds: 秒。0から59までの数値です。省略した場合、0として扱われます。
  • milliseconds: ミリ秒。0から999までの数値です。省略した場合、0として扱われます。

“`javascript
// 2023年10月27日午前10時30分0秒を指定
const dateParts = new Date(2023, 9, 27, 10, 30, 0, 0); // 月は9なので10月
console.log(dateParts); // 2023-10-27T10:30:00.000+09:00 (ローカルタイムでの出力例)

// 年と月のみ指定 (2024年1月1日)
const newYear = new Date(2024, 0); // 月は0なので1月、日時は省略され1日午前0時
console.log(newYear); // 2024-01-01T00:00:00.000+09:00 (ローカルタイム)

// 存在しない日付を指定した場合の挙動
// 2023年2月30日 (2月は28日または29日までしかない)
const invalidDate = new Date(2023, 1, 30); // 月は1なので2月
console.log(invalidDate); // 2023年3月2日として扱われる (オーバーフロー)
“`

この形式の利点は、各要素を数値で指定できるため、文字列パースのような曖昧さがないことです。ただし、月のインデックスが0始まりであること、そして指定された日付が存在しない場合は自動的に繰り上がって正しい日付として扱われる(例:2月30日が3月2日になる)という挙動を理解しておく必要があります。このオーバーフロー/アンダーフローの挙動は、日付の計算に利用されることもありますが、意図しない結果を招く可能性もあるため注意が必要です。

Dateオブジェクトのメソッド詳細

Dateオブジェクトを作成したら、そのインスタンスが持つ様々なメソッドを使用して、日時の要素を取得したり、設定したり、異なる形式に変換したりできます。これらのメソッドは、ローカルタイムを扱うものと、UTC(協定世界時)を扱うものに大別されます。

日時要素の取得 (Getters)

getDate(), getHours(), getFullYear() など、getで始まるメソッド群は、Dateオブジェクトが保持する日時情報から、年、月、日などの個別の要素を取得します。

ローカルタイムでの取得メソッド:

これらのメソッドは、Dateオブジェクトが表す日時をローカルタイムゾーンに変換して、各要素を返します。

  • getFullYear(): 4桁の年を返します (例: 2023)。
    javascript
    const date = new Date('2023-10-27T10:30:00'); // タイムゾーン指定なし (ローカルとして扱われる場合あり)
    console.log(date.getFullYear()); // 2023
  • getMonth(): 月を返します。0から11の数値で返されます (0: 1月, …, 11: 12月)。
    javascript
    console.log(date.getMonth()); // 9 (10月を表す)
  • getDate(): 月内の日を返します。1から31の数値です。
    javascript
    console.log(date.getDate()); // 27
  • getDay(): 曜日を返します。0から6の数値で返されます (0: 日曜日, 1: 月曜日, …, 6: 土曜日)。
    javascript
    console.log(date.getDay()); // 5 (金曜日を表す)
  • getHours(): 時を返します。0から23の数値です。
    javascript
    console.log(date.getHours()); // 10 (ローカルタイムでの時)
  • getMinutes(): 分を返します。0から59の数値です。
    javascript
    console.log(date.getMinutes()); // 30 (ローカルタイムでの分)
  • getSeconds(): 秒を返します。0から59の数値です。
    javascript
    console.log(date.getSeconds()); // 0 (ローカルタイムでの秒)
  • getMilliseconds(): ミリ秒を返します。0から999の数値です。
    javascript
    const now = new Date();
    console.log(now.getMilliseconds()); // 現在のミリ秒

UTC (協定世界時) での取得メソッド:

これらのメソッドは、Dateオブジェクトが表す日時をUTCとして解釈し、各要素を返します。メソッド名の先頭にUTCが付きます (getUTCFullYear(), getUTCMonth() など)。

  • getUTCFullYear(): UTCでの4桁の年を返します。
  • getUTCMonth(): UTCでの月を返します (0-11)。
  • getUTCDate(): UTCでの月内の日を返します (1-31)。
  • getUTCDay(): UTCでの曜日を返します (0-6)。
  • getUTCHours(): UTCでの時を返します (0-23)。
  • getUTCMinutes(): UTCでの分を返します (0-59)。
  • getUTCSeconds(): UTCでの秒を返します (0-59)。
  • getUTCMilliseconds(): UTCでのミリ秒を返します (0-999)。

“`javascript
const dateUTC = new Date(‘2023-10-27T10:30:00Z’); // UTCで指定された日時
console.log(dateUTC.getUTCHours()); // 10

const dateLocal = new Date(‘2023-10-27T10:30:00’); // ローカルタイムとして解釈 (環境依存)
// 日本標準時(JST, UTC+9)の場合
console.log(dateLocal.getHours()); // 10
console.log(dateLocal.getUTCHours()); // 1 (10時 – 9時間 = 1時)
“`

ローカルタイムとUTCのメソッドを使い分けることで、ユーザーのタイムゾーンに合わせて日時を表示したり、タイムゾーンに依存しない絶対的な時刻として日時を扱ったりできます。

その他の取得メソッド:

  • getTime(): UTCの1970年1月1日午前0時0分0秒からの経過ミリ秒数を返します。これはDateオブジェクトが内部的に保持する値そのものであり、日時を数値として比較したり、計算したりする際に非常に便利です。
    javascript
    const date1 = new Date(2023, 9, 27, 10, 30);
    const date2 = new Date(2023, 9, 28, 10, 30);
    const diffMilliseconds = date2.getTime() - date1.getTime();
    const diffDays = diffMilliseconds / (1000 * 60 * 60 * 24);
    console.log(`日付の差 (日数): ${diffDays}`); // 1
  • getTimezoneOffset(): ローカルタイムゾーンのUTCからのオフセットを分単位で返します。返される値は、ローカルタイムがUTCより進んでいる場合は負の値、遅れている場合は正の値になります。例えば、日本標準時 (JST) はUTC+9なので、getTimezoneOffset()-540 (=-9 * 60) を返します。
    javascript
    const now = new Date();
    console.log(`タイムゾーンオフセット (分): ${now.getTimezoneOffset()}`); // 例: -540 (JSTの場合)

日時要素の設定 (Setters)

setDate(), setHours(), setFullYear() など、setで始まるメソッド群は、既存のDateオブジェクトの特定の日時要素を変更します。これらのメソッドも、ローカルタイムを扱うものとUTCを扱うものがあります。要素を変更すると、そのDateオブジェクトが表す全体の日時が再計算されます。

ローカルタイムでの設定メソッド:

これらのメソッドは、日時の要素をローカルタイムで設定します。

  • setFullYear(year[, month[, day]]): 年、月、日を設定します。月と日は省略可能です。
    javascript
    const date = new Date(2023, 9, 27); // 2023年10月27日 ローカルタイム
    date.setFullYear(2024);
    console.log(date.getFullYear()); // 2024
    console.log(date); // 2024-10-27 ... (ローカルタイム)
  • setMonth(month[, day]): 月と日を設定します。月は0-11で指定します。日を省略した場合、その月の1日になります。指定した日が存在しない場合は、次の月に繰り越されます。
    “`javascript
    date.setMonth(0); // 1月に変更
    console.log(date.getMonth()); // 0
    console.log(date); // 2024-01-27 … (ローカルタイム)

    // 存在しない日付を指定
    const leapYear = new Date(2024, 1, 1); // 2024年2月1日 ローカルタイム
    leapYear.setDate(30); // 2024年2月30日を指定 -> 2024年3月1日になる
    console.log(leapYear.getDate()); // 1
    console.log(leapYear.getMonth()); // 2 (3月)
    - `setDate(day)`: 月内の日を設定します (1-31)。指定した日が存在しない場合は、月が繰り越されます。
    - `setHours(hours[, minutes[, seconds[, milliseconds]]])`: 時、分、秒、ミリ秒を設定します。省略された引数は0になります。指定可能な範囲外の値を指定すると、繰り上がり/繰り下がりが発生します (例: 24時を指定すると次の日の0時になる)。
    javascript
    const date = new Date(2023, 9, 27, 10); // 10時
    date.setHours(15, 30); // 15時30分に変更
    console.log(date.getHours()); // 15
    console.log(date.getMinutes()); // 30
    console.log(date); // 2023-10-27 15:30:00 … (ローカルタイム)

    date.setHours(date.getHours() + 24); // 24時間後 (次の日)
    console.log(date.getDate()); // 28
    console.log(date.getHours()); // 15
    ``
    -
    setMinutes(minutes[, seconds[, milliseconds]]): 分、秒、ミリ秒を設定します。
    -
    setSeconds(seconds[, milliseconds]): 秒、ミリ秒を設定します。
    -
    setMilliseconds(milliseconds)`: ミリ秒を設定します。

UTC (協定世界時) での設定メソッド:

これらのメソッドは、日時の要素をUTCで設定します。メソッド名の先頭にUTCが付きます (setUTCFullYear(), setUTCMonth() など)。挙動はローカルタイム版と同様ですが、UTC基準で変更が行われます。

  • setUTCFullYear(year[, month[, day]])
  • setUTCMonth(month[, day])
  • setUTCDate(day)
  • setUTCHours(hours[, minutes[, seconds[, milliseconds]]])
  • setUTCMinutes(minutes[, seconds[, milliseconds]])
  • setUTCSeconds(seconds[, milliseconds])
  • setUTCMilliseconds(milliseconds)

“`javascript
const date = new Date(2023, 9, 27, 10, 30); // ローカルタイムで作成 (例: 2023-10-27 10:30 JST)
console.log(date.toUTCString()); // Fri, 27 Oct 2023 01:30:00 GMT (UTCでは1時30分)

date.setUTCHours(12); // UTCの時を12時に変更
console.log(date.toUTCString()); // Fri, 27 Oct 2023 12:30:00 GMT
// ローカルタイムでは… (UTC+9の場合)
console.log(date.getHours()); // 21 (12 + 9 = 21時)
“`

エポックタイムの設定メソッド:

  • setTime(milliseconds): Dateオブジェクトが表す日時を、UTCの1970年1月1日午前0時0分0秒からの経過ミリ秒数で直接設定します。これはDateオブジェクトの内部値を直接変更する最も直接的な方法です。
    javascript
    const date = new Date(); // 現在日時
    const pastTime = date.getTime() - (1000 * 60 * 60); // 1時間前のミリ秒
    date.setTime(pastTime); // 日時を1時間前に設定
    console.log(date); // 1時間前の日時

setTime()は、getTime()で取得したミリ秒値を使って日付の計算を行った結果を反映させる際に便利です。

文字列への変換 (Formatters)

Dateオブジェクトを様々な文字列形式に変換するためのメソッドも多数用意されています。これらのメソッドは、デバッグ出力、API連携、ユーザーへの表示など、目的に応じて使い分けられます。

標準的な変換メソッド:

これらのメソッドの出力形式は比較的固定的ですが、完全に環境に依存しないわけではありません。

  • toString(): 人間が読める形式の文字列を返します。出力形式は実装依存です。通常、タイムゾーン情報を含みます。
    javascript
    const now = new Date();
    console.log(now.toString()); // 例: Fri Oct 27 2023 10:30:00 GMT+0900 (日本標準時)
  • toDateString(): 日付の部分のみを文字列で返します。
    javascript
    console.log(now.toDateString()); // 例: Fri Oct 27 2023
  • toTimeString(): 時間の部分のみを文字列で返します(タイムゾーン情報を含むことが多い)。
    javascript
    console.log(now.toTimeString()); // 例: 10:30:00 GMT+0900 (日本標準時)
  • toUTCString() (toGMTString() は非推奨): 日時をUTCとして、RFC 1123形式に似た文字列で返します。
    javascript
    console.log(now.toUTCString()); // 例: Fri, 27 Oct 2023 01:30:00 GMT

ロケールに依存した変換メソッド:

これらのメソッドは、ユーザーのロケール(地域設定)に基づいて、日付や時間の書式を設定して文字列を返します。出力形式は環境のロケール設定に大きく依存します。

  • toLocaleString(): 日付と時間をロケールに応じた形式で返します。
    javascript
    console.log(now.toLocaleString()); // 例 (ja-JPの場合): 2023/10/27 10:30:00
  • toLocaleDateString(): 日付のみをロケールに応じた形式で返します。
    javascript
    console.log(now.toLocaleDateString()); // 例 (ja-JPの場合): 2023/10/27
  • toLocaleTimeString(): 時間のみをロケールに応じた形式で返します。
    javascript
    console.log(now.toLocaleTimeString()); // 例 (ja-JPの場合): 10:30:00

    これらのメソッドは、オプション引数でロケールや書式を細かく指定することも可能ですが、より柔軟で標準的な国際化のためには後述のIntl.DateTimeFormatを使用することが推奨されます。

ISO 8601形式とJSON形式:

  • toISOString(): 日時をUTCとして、ISO 8601拡張形式 (YYYY-MM-DDTHH:mm:ss.sssZ) の文字列で返します。これはタイムゾーン情報 (Z はUTCを示す) を含み、プログラム間でのデータ交換に最も適した形式です。
    javascript
    const now = new Date();
    console.log(now.toISOString()); // 例: 2023-10-27T01:30:00.123Z
  • toJSON(): toISOString() と同じ文字列を返します。これはJSON.stringify()Dateオブジェクトをシリアライズする際に内部的に呼び出すメソッドです。
    javascript
    const now = new Date();
    console.log(JSON.stringify(now)); // 例: "2023-10-27T01:30:00.123Z"

toISOString()は、サーバーとのAPI通信や、ログ記録など、日時情報をプログラムで扱いたい場合に最も信頼性の高い形式です。Zが付いている場合、その時刻はUTCとして解釈されます。

静的メソッド

Dateオブジェクト自体(インスタンスではなくクラスとして)にも利用できる静的メソッドがあります。

  • Date.now(): 現在のUTCの1970年1月1日午前0時0分0秒からの経過ミリ秒数を返します。
    javascript
    const millisecondsSinceEpoch = Date.now();
    console.log(millisecondsSinceEpoch); // 現在のエポックタイム (数値)

    new Date().getTime()と似ていますが、Date.now()は新しいDateオブジェクトを作成する必要がないため、わずかに効率が良いとされています。現在時刻のタイムスタンプをミリ秒で取得したい場合に推奨される方法です。

  • Date.parse(dateString): 指定された日時文字列をパースし、UTCの1970年1月1日午前0時0分0秒からの経過ミリ秒数を返します。パースに失敗した場合はNaNを返します。
    “`javascript
    const epochTime = Date.parse(‘2023-10-27T10:30:00Z’); // ISO 8601 UTC
    console.log(epochTime); // 例: 1698393000000

    const invalidParse = Date.parse(‘invalid date string’);
    console.log(invalidParse); // NaN
    ``new Date(dateString)と似ていますが、こちらはDateオブジェクト自体ではなく、ミリ秒数を直接返します。文字列パースの際の注意点(特にISO 8601の使用推奨)はnew Date(dateString)`と同様に適用されます。

  • Date.UTC(year, month, day[, hours[, minutes[, seconds[, milliseconds]]]]): 引数として指定された年、月などの要素から、UTCの1970年1月1日午前0時0分0秒からの経過ミリ秒数を返します。引数の指定方法はnew Date(year, ...)形式と似ていますが、解釈がローカルタイムではなくUTCである点が異なります。月は0始まり、年は4桁必須です。
    javascript
    // UTCの2023年10月27日 10時30分0秒0ミリ秒を表すミリ秒を取得
    const utcMilliseconds = Date.UTC(2023, 9, 27, 10, 30, 0, 0); // 月は9なので10月
    console.log(utcMilliseconds); // 1698402600000

    このミリ秒値をnew Date()に渡せば、UTCで指定した正確な日時を持つDateオブジェクトを作成できます。
    new Date(Date.UTC(year, month, ...))

タイムゾーンとUTC: なぜUTCが重要なのか

日付と時間を扱う上で、タイムゾーンの概念は非常に重要です。JavaScriptのDateオブジェクトは、内部的にはUTCのエポックからのミリ秒数として時刻を管理していますが、メソッドの多くはローカルタイムとUTCの両方に対応しています。

  • ローカルタイム: スクリプトが実行されているコンピュータやデバイスのタイムゾーン設定に基づいた日時です。ユーザーへの表示など、その場所での正確な時間を伝えたい場合に便利です。しかし、ローカルタイムはデバイスの設定に依存するため、異なるユーザーやサーバー環境では異なる時刻として解釈される可能性があります。また、夏時間(サマータイム)の切り替えによって時刻がずれる可能性もあります。
  • UTC (Coordinated Universal Time – 協定世界時): 世界標準時です。タイムゾーンや夏時間の影響を受けない、普遍的な時刻基準です。

アプリケーションの内部で日時データを扱う際、特にサーバーとの連携や、複数のユーザー間で共通の日時を扱う必要がある場合は、UTCを基準として日時を管理することが強く推奨されます。

なぜなら、ローカルタイムで日時を保存・処理してしまうと、それを別のタイムゾーンや設定の環境で読み込んだ際に、元々の意図した時刻とは異なる時刻として解釈されてしまうリスクがあるためです。

  • 保存/送信時: 日時情報をデータベースに保存したり、APIでサーバーに送信したりする際は、toISOString()を使ってUTC形式の文字列に変換するか、getTime()で取得したエポックタイム(ミリ秒数)として保存/送信するのがベストプラクティスです。これらはタイムゾーンに依存しない普遍的な値です。
  • 受信/取得時: APIなどから日時情報を受け取る際は、それがどの形式・どのタイムゾーンであるかを明確に把握することが重要です。ISO 8601形式 (特に末尾にZが付いているもの) やエポックタイムで受け取るのが最も安全です。文字列形式で受け取った場合は、Date.parse()new Date(string)でパースすることになりますが、形式が不明確な場合は問題が生じる可能性があります。
  • 処理時: 日時情報の計算や比較を行う際は、一度UTCやエポックタイムに変換して行うと、タイムゾーンの影響を受けずに正確な処理ができます。
  • 表示時: 最終的にユーザーに日時を表示する際は、UTCや保存された時刻を元に、ユーザーのローカルタイムゾーンに合わせて表示します。これは、Dateオブジェクトのローカルタイム系メソッド (getHours(), toLocaleString() など) を利用するか、またはIntl.DateTimeFormatを使用することで実現できます。

例: ユーザーA (日本時間 JST, UTC+9) が「今日の午後5時」に設定したイベントを、ユーザーB (イギリス時間 GMT/BST, UTC+0/UTC+1) が見るとどうなるか?
ユーザーAが入力した「午後5時」をローカルタイムとして保存すると、「2023-10-27 17:00 (JST)」となります。これをそのままユーザーBの環境で表示すると、ローカルタイムの17:00として解釈され、「2023-10-27 17:00 (GMT/BST)」と表示されてしまいます。しかし、実際にはユーザーAの「午後5時 (JST)」はUTCで「午前8時」なので、ユーザーBのローカルタイムでは「午前8時 (GMT)」または「午前9時 (BST, 夏時間の場合)」となるべきです。

これを避けるためには、ユーザーAが入力した「午後5時 (ローカルタイム)」を、まずそのローカルタイムゾーンでの「特定の時点 (エポックタイム/UTC)」に変換して保存します。
「2023年10月27日 17:00 JST」は、UTCでは「2023年10月27日 08:00 UTC」に相当します。このUTC時刻またはそのエポックタイムを保存しておきます。
ユーザーBがその情報を見る際は、保存されたUTC時刻(またはエポックタイム)をユーザーBのローカルタイムゾーンに変換して表示します。
「2023年10月27日 08:00 UTC」は、イギリス時間では「2023年10月27日 08:00 GMT」(夏時間でない場合)または「2023年10月27日 09:00 BST」(夏時間の場合)となり、ユーザーBは意図された時刻を見ることができます。

JavaScriptのDateオブジェクトは、内部的にUTCを扱える機能 (getUTC..., setUTC..., toISOString(), getTime()) を持っているため、これらを活用してUTC基準で処理を進めることが、タイムゾーン問題を回避する鍵となります。

日付と時間の計算、操作、比較

Dateオブジェクトは直接的な算術演算子 (+, -) を使った計算はできません。しかし、getTime()メソッドで取得したエポックタイム(ミリ秒)は数値なので、これを使って計算を行うことができます。計算結果のミリ秒をnew Date(milliseconds)setTime(milliseconds)に戻すことで、新しいDateオブジェクトや既存のDateオブジェクトを変更できます。

日付の加算・減算

〇日後、〇時間後、〇分後といった計算は、現在のミリ秒に加算・減算を行うことで実現できます。

``javascript
const now = new Date();
console.log(
現在日時: ${now}`);

// 1日後を計算
const oneDayLater = new Date(now.getTime() + (1000 * 60 * 60 * 24));
console.log(1日後: ${oneDayLater});

// 3時間前を計算
const threeHoursAgo = new Date(now.getTime() – (1000 * 60 * 60 * 3));
console.log(3時間前: ${threeHoursAgo});

// 特定の日付から7日後
const specificDate = new Date(2023, 9, 27); // 2023年10月27日 ローカルタイム
specificDate.setDate(specificDate.getDate() + 7); // setDateは自動で月を繰り越す
console.log(2023-10-27 の7日後: ${specificDate}); // 2023年11月3日になる
``setDate(),setMonth()などのsetterメソッドは、指定された値が通常の範囲を超えていても自動的に繰り上がり/繰り下がりを処理してくれるため、日付の加算・減算に便利です。例えば、setDate(32)とすると、その月の32日ではなく、次の月の1日か2日など、正しい日付に調整されます。同様に、setMonth(12)`とすると、次の年の1月になります。

月の加算・減算と月末処理

月の加算・減算は、単純に setMonth(getMonth() + n) とすることで実現できますが、月末日に関する注意が必要です。例えば、1月31日に1ヶ月加算すると、2月31日は存在しないため、3月3日頃に繰り越されてしまいます。多くのケースでは、月の加算をしても日を保持したい(例:1月31日の1ヶ月後は2月28日/29日、3月31日の1ヶ月後は4月30日)と考えるでしょう。

このような場合、setMonth()を使った後に、元の日付がその月の末日よりも大きかったかをチェックし、必要に応じて月末日に調整する処理が必要になることがあります。しかし、これは少し複雑です。一般的には、月の加算・減算は、より堅牢な日付ライブラリを使用するか、または月末処理を考慮した独自のヘルパー関数を作成することが推奨されます。

日時の比較

Dateオブジェクトは、<, >, <=, >= といった比較演算子を使って直接比較できます。これは、Dateオブジェクトが内部的に保持するエポックタイム(ミリ秒)の数値で比較が行われるためです。

“`javascript
const date1 = new Date(2023, 9, 27);
const date2 = new Date(2023, 9, 28);

console.log(date1 < date2); // true
console.log(date1 > date2); // false
console.log(date1 <= date1); // true
“`

ただし、厳密な等価性チェック (===) は推奨されません。異なるDateインスタンスであっても、同じ日時を表している場合は「等しい」と判断したいことが多いでしょう。このような場合は、getTime()メソッドを使ってミリ秒値を比較します。

“`javascript
const dateA = new Date(2023, 9, 27, 10, 0, 0, 0);
const dateB = new Date(2023, 9, 27, 10, 0, 0, 0);
const dateC = new Date(2023, 9, 28, 10, 0, 0, 0);

console.log(dateA === dateB); // false (異なるインスタンスなので)
console.log(dateA.getTime() === dateB.getTime()); // true (同じ日時なので)
console.log(dateA.getTime() < dateC.getTime()); // true
“`

日時の差分計算

ある日時から別の日時までの期間(差分)を計算するには、それぞれのDateオブジェクトのgetTime()メソッドを使ってミリ秒を取得し、その差分を計算します。結果のミリ秒数を、秒、分、時、日などに変換することで、期間を求めることができます。

“`javascript
const startTime = new Date(‘2023-10-27T10:00:00Z’);
const endTime = new Date(‘2023-10-28T15:30:00Z’);

const diffMs = endTime.getTime() – startTime.getTime();
console.log(差分 (ミリ秒): ${diffMs});

const diffSeconds = diffMs / 1000;
console.log(差分 (秒): ${diffSeconds});

const diffMinutes = diffMs / (1000 * 60);
console.log(差分 (分): ${diffMinutes});

const diffHours = diffMs / (1000 * 60 * 60);
console.log(差分 (時間): ${diffHours});

const diffDays = diffMs / (1000 * 60 * 60 * 24);
console.log(差分 (日数): ${diffDays}); // 例: 1.229166…
“`
日数を計算した場合、小数点以下が出ることがあります。これは、計算が厳密なミリ秒単位で行われるためです。例えば、DST (夏時間) の切り替え日をまたぐ場合、1日の長さが24時間でなくなるため、単純な日数計算が意図しない結果を招く可能性があります。期間計算においても、可能な限りUTC基準で行うか、信頼できるライブラリの利用を検討するのが安全です。

日時文字列のパースに関する注意点

前述の通り、new Date(dateString)Date.parse(dateString) による文字列パースは、JavaScriptのDateオブジェクトにおける最大の落とし穴の一つです。

ISO 8601形式を強く推奨する理由

ECMAScript仕様では、Date.parse()が解釈すべき文字列形式として、ISO 8601のシンプルな形式 (YYYY-MM-DDTHH:mm:ss.sssZYYYY-MM-DD) を含むいくつかの形式が挙げられています。ISO 8601形式は、タイムゾーン情報を明確に指定でき、国際的な標準であるため、異なる環境間での互換性が最も高いです。

  • YYYY-MM-DD (例: “2023-10-27”): 日付のみ。タイムゾーン情報は指定されていません。この形式がUTCとして扱われるかローカルタイムとして扱われるかは、仕様が曖昧であり、実装依存となる可能性があります。ほとんどの環境ではローカルタイムとして扱われる傾向がありますが、信頼すべきではありません。
  • YYYY-MM-DDTHH:mm:ss.sss (例: “2023-10-27T10:30:00.000”): 日付と時刻。タイムゾーン情報は指定されていません。これも実装依存で、ローカルタイムまたはUTCとして扱われる可能性があります。
  • YYYY-MM-DDTHH:mm:ss.sssZ (例: “2023-10-27T10:30:00.000Z”): 日付、時刻、そしてタイムゾーン (Z はUTCを示す)。この形式はUTCとして明確に解釈されるため、最も推奨されます。
  • YYYY-MM-DDTHH:mm:ss.sss+HH:mm または -HH:mm (例: “2023-10-27T10:30:00.000+09:00”): 日付、時刻、タイムゾーンオフセット。指定されたタイムゾーンオフセットで解釈されます。

非標準形式のリスク

  • MM/DD/YYYY または DD/MM/YYYY (例: “10/27/2023” または “27/10/2023”): この形式は、月と日のどちらが先に指定されているかが曖昧です。米国の多くのシステムはMM/DD/YYYYを期待しますが、ヨーロッパやその他の地域ではDD/MM/YYYYが一般的です。JavaScriptエンジンも実装によってどちらかに固定されていたり、ヒューリスティック(推測)で判断したりするため、予期しない結果になる可能性が高いです。この形式は絶対に使用しないでください。
    javascript
    // 実行環境によって結果が異なる可能性があります!
    console.log(new Date('10/27/2023')); // 10月27日?
    console.log(new Date('27/10/2023')); // 10月27日?
  • ハイフン区切り vs スラッシュ区切り: ISO 8601はハイフン区切り (YYYY-MM-DD) を使用します。スラッシュ区切り (YYYY/MM/DD) は、ISO 8601には含まれませんが、多くの環境でパース可能です。ただし、スラッシュ区切りはローカルタイムとして扱われる傾向があるなど、ハイフン区切り (ISO 8601) とは挙動が異なる場合があります。可能な限りハイフン区切りのISO 8601を使用すべきです。
    “`javascript
    const hyphenDate = new Date(‘2023-10-27’); // ローカルタイムとして扱われる傾向
    const slashDate = new Date(‘2023/10/27’); // ローカルタイムとして扱われる傾向

    console.log(hyphenDate.toISOString()); // タイムゾーンオフセットが付加される
    console.log(slashDate.toISOString()); // タイムゾーンオフセットが付加される
    “`
    タイムゾーン情報がない文字列がローカルタイムとして扱われるという挙動は、ECMAScript 2015 (ES6) から導入されたもので、それ以前はUTCとして扱われる環境もありました。このような歴史的な経緯もあり、文字列パースは常に注意が必要です。

Date.parse()の活用と限界

Date.parse()は文字列をパースしてミリ秒値を返します。new Date(string)は内部的にDate.parse()を呼び出していることが多いです。Date.parse()は、パースに失敗した場合にNaNを返すという明確な挙動を示します。これは、new Date(invalidString)Invalid DateというDateオブジェクト(getTime()NaNを返す)を返すのと似ています。

文字列パースに頼る場合、常にパースが成功したか (isNaN(date.getTime()) または isNaN(Date.parse(string))) をチェックする、そして入力文字列が信頼できる形式(特にISO 8601)であることを確認することが非常に重要です。

Dateオブジェクト利用時の一般的な落とし穴と注意点

これまでの説明で触れてきたものも含め、Dateオブジェクトを使う上で特に注意すべき点をまとめます。

  1. 月は0から始まる: getMonth()は0-11を返し、setMonth()は0-11を受け取ります。ユーザー入力(1-12)を扱う際は、必ず1を減算/加算する必要があります。これはおそらく最も頻繁に発生するエラーの原因です。
    javascript
    const date = new Date(2023, 9, 1); // 10月1日を指定
    console.log(date.getMonth()); // 9
  2. 曜日は0から始まる (日曜が0): getDay()は0-6を返します (日曜日: 0, 月曜日: 1, …, 土曜日: 6)。これも一般的な感覚(月曜日始まりなど)と異なる場合があるため、注意が必要です。
  3. タイムゾーンの曖昧さ (夏時間など): ローカルタイムで日時を扱う場合、夏時間の開始・終了日や、タイムゾーンルールの変更によって予期しない時刻のずれが生じる可能性があります。内部処理では可能な限りUTCを使用し、表示時のみローカルタイムに変換するのが安全です。
  4. 文字列パースのブラウザ/環境依存性: ISO 8601形式以外の文字列パースは、ブラウザやNode.jsなどのJavaScript実行環境によって挙動が異なる可能性が高いです。安全のため、信頼できないソースからの日時文字列は、自身でパースするか、ISO 8601形式に整形してからDateオブジェクトに渡すべきです。
  5. うるう年、うるう秒: Dateオブジェクトはうるう年(2月29日)を正しく扱いますが、うるう秒はサポートしていません。通常、うるう秒はアプリケーション開発で考慮する必要はほとんどありませんが、極めて高精度な時刻同期が必要なシステムでは注意が必要です。
  6. 2桁年問題 (過去の遺物だが注意): new Date(year, ...) 形式で年を2桁で指定すると、1900年代や2000年代のどちらとして解釈されるかが環境によって異なったり、予期しない年になったりする可能性があります。必ず4桁で年を指定してください。
    javascript
    // 絶対に避けるべきコード
    const date = new Date(99, 0, 1); // 1999年か2099年か?環境依存
    console.log(date.getFullYear());
  7. 不変性がない: Dateオブジェクトはミュータブル(変更可能)です。set...系のメソッドを呼び出すと、元のオブジェクト自体が変更されます。これは、意図しない副作用を引き起こす可能性があるため、注意が必要です。特定の時点を保持しておきたい場合は、変更前にコピーを作成するか、新しいDateインスタンスを作成する必要があります。
    “`javascript
    const originalDate = new Date(2023, 9, 27);
    const modifiedDate = originalDate; // 参照がコピーされるだけ

    modifiedDate.setDate(28);

    console.log(originalDate.getDate()); // 28 (originalDateも変更されてしまう!)
    console.log(modifiedDate.getDate()); // 28

    // 正しいコピーの方法
    const originalDate2 = new Date(2023, 9, 27);
    const copiedDate = new Date(originalDate2.getTime()); // ミリ秒で新しいインスタンスを作成

    copiedDate.setDate(28);

    console.log(originalDate2.getDate()); // 27 (変更されない)
    console.log(copiedDate.getDate()); // 28
    “`

より高度な日付と時間の扱い

JavaScriptの組み込みDateオブジェクトは、基本的な日時操作には十分ですが、複雑な要件(特定の形式での書式設定、タイムゾーン変換、期間計算など)に対応するには機能が不足していたり、前述のような落とし穴があったりします。より高度な日付と時間の扱いが必要な場合は、以下の選択肢を検討することをお勧めします。

Intl.DateTimeFormat を利用した国際化

ECMAScript Internationalization API (Intl) の一部であるIntl.DateTimeFormatは、ロケールに応じた柔軟な日付・時間書式設定を提供します。これはtoLocaleString()などの組み込みメソッドよりもはるかに高機能で信頼性があります。

“`javascript
const now = new Date();

// 日本語 (日本) ロケールでフルフォーマット
const formatterJP = new Intl.DateTimeFormat(‘ja-JP’, {
year: ‘numeric’,
month: ‘long’,
day: ‘numeric’,
hour: ‘numeric’,
minute: ‘numeric’,
second: ‘numeric’,
timeZoneName: ‘short’
});
console.log(formatterJP.format(now)); // 例: 2023年10月27日 10:30:00 JST

// 英語 (米国) ロケールで短い日付フォーマット
const formatterEN = new Intl.DateTimeFormat(‘en-US’, {
year: ‘2-digit’,
month: ‘numeric’,
day: ‘numeric’
});
console.log(formatterEN.format(now)); // 例: 10/27/23

// オプションでタイムゾーンを指定することも可能
const formatterUTC = new Intl.DateTimeFormat(‘en-US’, {
year: ‘numeric’,
month: ‘short’,
day: ‘numeric’,
hour: ‘numeric’,
minute: ‘numeric’,
timeZone: ‘UTC’,
timeZoneName: ‘short’
});
console.log(formatterUTC.format(now)); // 例: Oct 27, 2023, 1:30 AM UTC
``Intl.DateTimeFormat`は、ユーザーインターフェースに日時を表示する際に非常に強力なツールです。

外部ライブラリの検討

より複雑な日付操作(月末処理、特定の曜日を基準にした計算、期間の正確な計算、高度なパース、タイムゾーン変換、国際化対応など)が必要な場合は、成熟した外部ライブラリの利用を検討するのが現実的です。これにより、Dateオブジェクトの潜在的な問題を回避し、より可読性が高く堅牢なコードを書くことができます。

かつてMoment.jsが広く使われていましたが、現在はメンテナンスモードに入っており、新規開発には推奨されません。代わりとして、以下のようなライブラリが推奨されています。

  • date-fns: モダンなJavaScript開発に適した、不変性を持つ関数型のライブラリです。必要な機能だけをインポートして使えるため、バンドルサイズを小さく保てます。
  • Luxon: Moment.jsの作者の一人によって開発されたライブラリで、不変性とタイムゾーンへの優れた対応が特徴です。
  • Day.js: Moment.jsと互換性のあるAPIを持ちつつ、非常に軽量なライブラリです。

これらのライブラリは、Dateオブジェクトのラッパーとして機能し、より直感的で安全なAPIを提供します。

標準化の動向 (Temporal API)

ECMAScriptの次世代標準として、新しい日付・時間APIである Temporal の策定が進められています。Temporalは、現在のDateオブジェクトの多くの問題を解決するために設計されており、不変性、明確なタイムゾーン処理、高精度な日時表現、期間の計算など、より強力で使いやすい機能を提供する予定です。

Temporalはまだ標準化の最終段階ではなく、すべての環境で利用できるわけではありませんが、将来的にJavaScriptで日付・時間を扱う標準的な方法となることが期待されています。最新の開発状況を追っておくと良いでしょう。

まとめ

JavaScriptのDateオブジェクトとnew Date()は、現在日時や特定の日時を扱うための基本的な機能を提供します。

  • new Date()には、引数なし(現在日時)、ミリ秒、文字列、複数の数値引数(ローカルタイム)の4つの主要な形式があります。
  • Dateオブジェクトのメソッドは、ローカルタイムとUTCの両方で日時要素の取得 (get..., getUTC...) や設定 (set..., setUTC...) を行えます。
  • getTime()はエポックからのミリ秒数を返し、数値計算や比較に役立ちます。
  • toISOString()toJSON()は、プログラム間のデータ交換に最適なUTCのISO 8601形式文字列を返します。
  • Date.now()は現在のエポックミリ秒数を効率的に取得できます。
  • Date.parse()Date.UTC()は静的メソッドとして利用できます。

Dateオブジェクトを安全かつ正確に利用するためには、以下の点に注意が必要です。

  • 月は0始まり、曜日は0始まりであることを常に意識する。
  • 内部処理やデータ交換ではUTCまたはエポックタイムを基準とする(getUTC..., setUTC..., toISOString(), getTime(), Date.now(), Date.UTC()を活用)。
  • 日時文字列のパースは危険性が高いため、可能な限りISO 8601形式 (特にタイムゾーン付きのUTC形式) を使用し、他の形式は避ける。パース結果は必ず検証する (isNaN(date.getTime()))。
  • Dateオブジェクトは変更可能であることに注意する。
  • 複雑な書式設定にはIntl.DateTimeFormatを検討する。
  • より高度な操作や潜在的な問題を回避したい場合は、date-fns, Luxonなどの外部ライブラリの利用を強く推奨する。

Dateオブジェクトは一見シンプルですが、タイムゾーンや文字列パースなどの要素が絡むと複雑になりがちです。これらの注意点とベストプラクティスを理解することで、日付・時間に関連するバグを減らし、より堅牢なアプリケーションを構築できるでしょう。この記事が、JavaScriptで日時を扱う際の理解を深める一助となれば幸いです。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール