タイムゾーンで失敗しないDate APIの使い方 前編 日時処理の基礎知識

このシリーズでは、JavaScriptのDateオブジェクトを用いた日時処理において、タイムゾーンを正しく扱う方法を解説します。まずは、よくある実装の落とし穴を確認し、時差とDateの関係について理解を深めます。

発行

著者 森 大典 フロントエンド・エンジニア
タイムゾーンで失敗しないDate APIの使い方 シリーズの記事一覧

はじめに

JavaScriptのDateオブジェクトは日時を扱う標準APIとして多くのプロジェクトで利用されています。しかし、タイムゾーンの扱いに関する理解が不十分なまま実装してしまうと、開発環境では正しく動作していたコードが、ユーザーの環境や状況によって意図しない挙動を引き起こすことがあります。

本シリーズでは、JavaScriptのDateオブジェクトを用いた日時処理において、タイムゾーンを正しく扱う方法を解説します。

前編では、よくある実装の落とし穴を確認した上で、時差・タイムゾーン・PCの時間管理システムとDateオブジェクトの関係について基礎から理解を深めます。

Dateのよくある実装とその落とし穴

とあるプロジェクトで「APIから日本時間の発注可能日時を取得し、その日時が現在日時をすでに経過していれば注文ボタンを有効化する」という要件が生じたとします。

このようなケースの実装として、思い浮かぶもののひとつとして、Dateオブジェクトがあります。Dateオブジェクトは古くからある標準APIで、過去に何となく使った記憶がある方も多いのではないでしょうか?

上記のような要件では、Dateオブジェクトは特定の日時のみでなく、現在日時も取得可能であることを覚えていれば、わざわざライブラリを導入する必要はないと判断し、次のような実装を思いつくかもしれません。

Dateを使った発注可能状況を判定するコード

// APIから取得した発注可能日時(日本時間)
const orderableAt = '2025-11-01 18:00:00';
const orderableDate = new Date(orderableAt);

// 発注可能かどうかの判定
const isOrderable = new Date() >= orderableDate;

このコードは、実行してみると問題なく動作しているように見えるでしょう。しかし、PCの設定によっては、旅行や出張などで海外に行った際、注文ボタンの有効化タイミングがずれてしまうという問題が発生します。たとえばロンドンでは9時間、ニューヨークでは14時間もずれることになります。

その原因を知るには、時差・タイムゾーン・PCの時間管理システムとDateオブジェクトの関係を理解する必要があります。順番に確認していきましょう。

時差とタイムゾーン

地球は丸くて自転しているため、場所ごとに太陽が昇る時刻が違います。つまり、日本で朝7時に太陽が昇っているとき、地球の反対側ではすでに夜になっています。そこで、「朝」「昼」「夜」の感覚を保つために、それぞれの地域では時間をずらして設定しています。

この考え方の基準になっているのが、イギリス・グリニッジ天文台を通る経度0度の線(本初子午線)です。 現在は、経度0度を基準とした「協定世界時(UTC)」が世界の基準時として使われており、原子時計に基づいて正確に計測されています。各地域はUTCから時間を進めたり遅らせたりして独自の時刻を設定し、その差を「時差」と呼んでいます。

対してタイムゾーンとは、地球上の特定の地域で使用される時間のルール(標準時)のことで、各地域は、UTCからの時差に基づいて独自のタイムゾーンを持っています。たとえば、日本はUTC+9時間のタイムゾーンに属しており、これを「日本標準時(JST)」と呼びます。

タイムゾーンは、地域ごとの社会的・経済的活動に合わせて設定されており、夏時間(DST)を採用している地域もあります。夏時間では、日照時間を有効活用するために、標準時を1時間進めることがあります。

地域 タイムゾーン 時差
日本 JST(日本標準時) UTC+9
ロンドン UTC(協定世界時) UTC+0
ニューヨーク EST(東部標準時) UTC−5(夏は−4)

PCの時間管理システム

Webブラウザが実行されているPCの時間管理システムはどのようになっているでしょうか。

PCは内部に時計を持っており、多くのOSではこれをUTC基準で保持し、インターネット上の時刻サーバーと同期してUTCの絶対時刻を正確に維持しようとします。このUTC時刻は、UNIXエポックと呼ばれる時間の基準点(起点)である「1970年1月1日 00:00:00(UTC)」からの経過時間を、ミリ秒で表現したUNIXタイムという絶対値で管理されます。

そして、このUNIXタイムは、タイムゾーン設定としてOSに設定された地域(例:Asia/Tokyo)に従い、ローカル時刻へ変換され表示されます。つまり、PCはどの地域に存在しようがUTCで時間を管理し、タイムゾーン設定に応じて表示だけを変えているわけです。

日時(UTC) UNIXタイム(ミリ秒) 日本(UTC+9)での表示時刻 ロンドン(UTC+0)での表示時刻
1970-01-01 00:00:00 0 1970-01-01 09:00:00 1970-01-01 00:00:00
1970-01-01 00:00:01 1,000 1970-01-01 09:00:01 1970-01-01 00:00:01
1970-01-01 00:01:00 60,000 1970-01-01 09:01:00 1970-01-01 00:01:00

また、最近のPCは「現在地に基づいてタイムゾーンを自動調整」する機能があり、デフォルトで有効になっています。これにより、海外にPCを持って行った場合でもタイムゾーン設定が更新され、自動的に現地時刻の表示に切り替わります。

Dateオブジェクトと時差の関係

Dateオブジェクトは、時間軸上の特定の瞬間をタイムスタンプとしてカプセル化するオブジェクトです。前節で述べたPCの時間管理システムと同様に、内部的にはUTCをUNIXタイムとして保持する仕組みになっています。

一方で、Dateのインスタンス生成や日時の取得・更新といった操作は、コードの書き方によってOSのタイムゾーン設定の影響を受ける場合と、受けない場合があります。そこで、まず、タイムゾーン設定の影響を受けるインスタンス生成の書き方から見てみましょう。

たとえば、OSのタイムゾーン設定が日本時間(UTC+9)の時と、ロンドン(UTC+0)の場合に、UNIXエポックに+9時間1秒を加えた日時のDateオブジェクトからUNIXタイムを取得すると、それぞれ次のような結果になります。

タイムゾーン設定が日本時間(UTC+9)

const d = new Date('1970-01-01 09:00:01');
d.getTime(); // 1000

Dateオブジェクトはインスタンス生成時に、UNIXタイムを内部的に保持します。この例では、「09:00:01」から日本の時差である9時間を引いた「00:00:01」をUTC日時と解釈するため、UNIXタイムは1000ミリ秒となりインスタンスに保持されます。getTime()は、保持されたUNIXタイムをそのまま返しているだけです。

タイムゾーン設定がロンドン(UTC+0)

const d = new Date('1970-01-01 09:00:01');
d.getTime(); // 32401000

対してタイムゾーン設定がロンドンの場合は時差が0のため、「09:00:01」という時刻をそのままUTC日時として解釈し、その結果として32401000ミリ秒というUNIXタイムを保持します。

このように、同じ'1970-01-01 09:00:01'という文字列でも、タイムゾーン設定によって異なるUNIXタイムが生成されてしまうことがわかります。

時差を明示したDateオブジェクトの生成

冒頭で提示したサンプルコードの仕様は、APIから取得した発注可能日時が日本時間(JST)基準であるとしていました。その一方で、前節で見たようなOSのタイムゾーン設定に依存する実装になっているため、記事冒頭で述べたように所在地によっては意図した動作になりません。

問題のあるコード(再掲)

// APIから取得した発注可能日時(日本時間)
const orderableAt = '2025-11-01 18:00:00';
const orderableDate = new Date(orderableAt);

// 発注可能かどうかの判定
const isOrderable = new Date() >= orderableDate;

この問題を解決するには、タイムゾーン設定の影響を受けさせずに、常にJST基準でUNIXタイムを生成する必要があります。

これを実現するには、次のようにISO 8601形式(YYYY-MM-DDTHH:mm:ss.sss±HH:mm)で時差を明示的に指定する方法があります。

時差を明示的に指定

// APIから取得した発注可能日時(日本時間)
const orderableAt = '2025-11-01T18:00:00+09:00'; // ★時差を明示
const orderableDate = new Date(orderableAt);

// 発注可能かどうかの判定
const isOrderable = new Date() >= orderableDate;

年月日と時間の間に「T」を入れ、日時文字列の末尾に「+09:00」を付与することで、DateオブジェクトはJST基準でUNIXタイム(指定日時から9時間の時差を差し引いた時間)を保持します。もし、APIから得られる日時がUTC基準である場合は、次のように「Z」を付与します。

UTC基準を明示する場合

const orderableAt = '2025-11-01T09:45:25Z'; // ★UTC基準を明示

このような記述により、Dateの持つUNIXタイムはタイムゾーン設定の影響を受けず、明示された時差でのみ時差補正されます。

そして、最後の行のDateオブジェクト同士の比較は、内部的にはUNIXタイム同士の比較になるため、次のようなコードと同等の内容になります。

UNIXタイムでの比較

const isOrderable = new Date().getTime() >= orderableDate.getTime();

また、現在時刻のUNIXタイムを取得するにはDate.now()という専用のメソッドも用意されているので、次のように記述するとより明示的なコードになります。

Date.now()を使った比較

const isOrderable = Date.now() >= orderableDate.getTime();

orderableDate.getTime()はJST基準の日時から生成されたUNIXタイムを返し、Date.now()はタイムゾーン設定に関係なく現在のUNIXタイムを返します。どちらもUNIXタイムであるため、これらの比較は常に同じ基準で行われることになり、冒頭で提示した要件を正しく満たすことができます。

時差指定では対応できないケース

本記事では、ISO 8601形式で+09:00のように時差を明示する方法を紹介しました。しかし、サマータイム(夏時間)を採用している地域では、時期によって時差が変わるため、時差だけでは不十分なケースがあります。

たとえば、ニューヨークの標準時は冬がUTC-5、夏がUTC-4です。つまり、「ニューヨークの2025年7月1日12:00」という日時を扱う場合、その日がサマータイム期間かどうかを自分で判断し、-04:00または-05:00を指定する必要があります。

このような場合、時差ではなくタイムゾーンID(例:America/New_York)を指定できれば、サマータイムの自動判定が可能になります。

日時の表示だけであれば、Intl.DateTimeFormatでタイムゾーンIDを指定することができます。しかし、Dateオブジェクトの生成(「ニューヨークの2025年7月1日12:00」からDateを作る)や日時の計算には、Dateオブジェクト単体では対応していません。

そのような用途には、date-fns-tzLuxonといったライブラリの利用が現実的です。また、次世代のDate APIであるTemporalも策定が進んでおり、将来的にはブラウザの標準機能として利用できるようになる見込みです。

タイムゾーン依存が必要なケース

一方で、Dateがタイムゾーンに依存する仕様になっているのは、ユーザーの現地時間を扱うためです。

たとえば、カレンダーUIで現地時刻基準で特定の日付を選択可能にする場合、ニューヨークにいるユーザーにはニューヨーク時刻として、東京にいるユーザーには東京時刻として処理する必要があります。このような場合、Dateがタイムゾーン設定を反映する動作は正しい挙動です。

つまり、Dateの使い分けとしては次のようになります。

  • APIから特定タイムゾーン基準の日時を扱う場合:ISO 8601形式で時差を明示
  • ユーザーの現地時間を扱う場合:タイムゾーン依存の動作を活用

まとめ

本記事では、Dateオブジェクトとタイムゾーンの関係について解説しました。要点を整理すると次のようになります。

  • Dateオブジェクトは内部的にUNIXタイムを保持している
  • new Date('2025-11-01 18:00:00')のようにタイムゾーン情報のない文字列での生成は、タイムゾーン設定に依存する
  • APIから受け取った日時を正しく扱うには、ISO 8601形式で時差を明示する必要がある

次回は、Dateオブジェクトのメソッドのうち、タイムゾーン設定の影響を受けるもの・受けないものを整理し、実践的な実装パターンを紹介します。