PHP の DateTime における、コンストラクタとタイムゾーンの関係ついて解説します。
実は「指定したはずのタイムゾーンが無視される」挙動をすることもあり、意外と油断なりません。その点についてもしっかりと解説してきます。
前提条件
この記事における PHP のデフォルトタイムゾーンは Asia/Tokyo とします。これは DateTime でタイムゾーンが未指定だったときのデフォルト値として使われます。
Case 1. now にタイムゾーンを指定する
まず最初に、現在日時(now)で DateTime を作ったときの実例を見てみましょう。
現在日時は「日本時間で 2025年2月17日 午前9時」であると仮定します。
// Case 1-1. タイムゾーンの指定なし
$nowJst = new DateTime('now');
var_dump($nowJst->format('Y-m-d H:i:s, e, U'));
// => string(43) "2025-02-17 09:00:00, Asia/Tokyo, 1739750400"
// Case 1-2. 第2引数でタイムゾーンを指定する
$nowUtc1 = new DateTime('now', new DateTimeZone('UTC'));
var_dump($nowUtc1->format('Y-m-d H:i:s, e, U'));
// => string(36) "2025-02-17 00:00:00, UTC, 1739750400"
// Case 1-3. タイムゾーンを後から変換する
$nowUtc2 = new DateTime('now');
$nowUtc2->setTimezone(new DateTimeZone('UTC'));
var_dump($nowUtc2->format('Y-m-d H:i:s, e, U'));
// => string(36) "2025-02-17 00:00:00, UTC, 1739750400"
ポイント
- 第2引数が省略された場合、デフォルトのタイムゾーンである Asia/Tokyo が使われる。
- 第2引数が指定された場合、そのタイムゾーンが使われる。
- UNIX時間 (1970年1月1日 0時0分0秒 からの経過秒数)はどのケースでも同じ値になる。
- now はタイムゾーンの指定に関わらず同じUNIX時間が生成される。
コンストラクタでタイムゾーンを指定したかどうかに関わらず、 UNIX時間は同じ値になります。また setTimeZone() で後からタイムゾーンを変換してもUNIX時間は変化しません。
なお format() で出力される日時は、現在設定されているタイムゾーンを反映したものになります。日本時間では午前9時に、UTCでは午前0時になっているのは、 format() 時点のタイムゾーンの違いによるものです。
Case 2. 年月日時分秒のみを指定する
次に、年月日時分秒を指定して DateTime を作ったときの実例を見てみましょう。
今回のケースでは、コンストラクタで「2025年2月17日 午前9時」を指定します。
// Case 2-1. タイムゾーンの指定なし
$nowJst = new DateTime('2025-02-17 09:00:00');
var_dump($nowJst->format('Y-m-d H:i:s, e, U'));
// => string(43) "2025-02-17 09:00:00, Asia/Tokyo, 1739750400"
// Case 2-2. 第2引数でタイムゾーンを指定する
$nowUtc1 = new DateTime('2025-02-17 09:00:00', new DateTimeZone('UTC'));
var_dump($nowUtc1->format('Y-m-d H:i:s, e, U'));
// => string(36) "2025-02-17 09:00:00, UTC, 1739782800"
// Case 2-3. タイムゾーンを後から変換する
$nowUtc2 = new DateTime('2025-02-17 09:00:00');
$nowUtc2->setTimezone(new DateTimeZone('UTC'));
var_dump($nowUtc2->format('Y-m-d H:i:s, e, U'));
// => string(36) "2025-02-17 00:00:00, UTC, 1739750400"
var_dump($nowUtc1 <=> $nowUtc2);
// => int(1)
var_dump($nowJst <=> $nowUtc2);
// => int(0)
ポイント
- UNIX時間は「第1引数の日時を、タイムゾーンで解釈した値」から算出される。
- 明示的にタイムゾーンが指定された場合はその値が使われ、そうでない場合はデフォルトのタイムゾーンが使われる。
- setTimeZone() を呼び出しても UNIX時間は変化しない。
- UNIX時間が同じ場合、タイムゾーンを問わず DateTime は等価とみなされる。
Case 1 で述べた通り now はタイムゾーンによる解釈が変わらない値でした。
一方で、今回のような「2025年2月17日 午前9時」という値はタイムゾーンによって解釈が変化する値です。算出されるUNIX時間もまた変わります。
この違いにより、 Case 2 では以下のようなUNIX時間の差異が発生しています。
- 「タイムゾーンを省略した場合(Case 2-1)」と「タイムゾーンをコンストラクタで指定した場合(Case 2-2)」でUNIX時間が異なる。
- 「タイムゾーンをコンストラクタで指定した場合(Case 2-2)」と「タイムゾーンを後から変更した場合(Case 2-3)」でUNIX時間が異なる。
Case 3. today を指定する
now と似た値で today という値を使うこともできます。しかし、両者の挙動は大きく異なります。
// 現在日時は「日本時間で 2025年2月17日 午前9時」であると仮定します。
// Case 3-1. タイムゾーンの指定なし
$nowJst = new DateTime('today');
var_dump($nowJst->format('Y-m-d H:i:s, e, U'));
// => string(43) "2025-02-17 00:00:00, Asia/Tokyo, 1739718000"
// Case 3-2. 第2引数でタイムゾーンを指定する
$nowUtc = new DateTime('today', new DateTimeZone('UTC'));
var_dump($nowUtc->format('Y-m-d H:i:s, e, U'));
// => string(36) "2025-02-17 00:00:00, UTC, 1739750400"
ポイント
- today は「その日の日時を0時0分0秒にしたもの」とみなされます。
- 「0時0分0秒」の解釈はタイムゾーンによって変わります。
today はタイムゾーンによって解釈が変わる値です。その結果、概ね Case 2 と同様の結果になります。
なお yesterday, tomorrow, noon といった値も同様に、タイムゾーンによって変わる値です。
Case 4. タイムゾーンありの日時を指定する
今までは DateTime の第2引数でタイムゾーンを指定していましたが、実は第1引数でタイムゾーンを指定する方法もあります。
第1引数に以下のような値を渡すことで、タイムゾーンありの日時を指定することができます。
- now UTC
- 2025-02-17T09:00:00+00:00
これらの値を使ったとき、生成される DateTime は以下のようになります。
// 現在日時は「日本時間で 2025年2月17日 午前9時」であると仮定します。
// Case 3-1. 第1引数のみタイムゾーンを指定し、現在時刻を生成する
$nowCase31 = new DateTime('now UTC');
var_dump($nowCase31->format('Y-m-d H:i:s, e, U'));
// => string(36) "2025-02-17 00:00:00, UTC, 1739750400"
// Case 3-2. 第1引数と第2引数の両方でタイムゾーンを指定し、現在時刻を生成する
$nowCase32 = new DateTime('now UTC', new DateTimeZone('America/Los_Angeles'));
var_dump($nowCase32->format('Y-m-d H:i:s, e, U'));
// => string(36) "2025-02-17 00:00:00, UTC, 1739750400"
// Case 3-3. 第1引数のみタイムゾーンを指定し、指定された年月日を生成する
$nowCase33 = new DateTime('2025-02-17T09:00:00+00:00');
var_dump($nowCase33->format('Y-m-d H:i:s, e, U'));
// => string(39) "2025-02-17 09:00:00, +00:00, 1739782800"
// Case 3-4. 第1引数と第2引数の両方でタイムゾーンを指定し、指定された年月日を生成する
$nowCase34 = new DateTime('2025-02-17T09:00:00+00:00', new DateTimeZone('America/Los_Angeles'));
var_dump($nowCase34->format('Y-m-d H:i:s, e, U'));
// => string(39) "2025-02-17 09:00:00, +00:00, 1739782800"
ポイント
- 第1引数でタイムゾーンが指定された場合、第2引数やデフォルトのタイムゾーンは無視される。
結果はいたってシンプルで、単に第1引数のタイムゾーンのみが使用されます。この挙動は PHP 公式のマニュアルにも記載されています。
https://www.php.net/manual/ja/datetime.construct.php より
注意:
\$datetime パラメータが UNIX タイムスタンプ (@946684800 など) であったりタイムゾーンつきで指定した場合 (2010-01-28T15:00:00+02:00 など) は、$timezone パラメータや現在のタイムゾーンは無視されます。
まとめ
この記事ではPHP の DateTime クラスの仕様について、主に DateTime コンストラクタ を中心に説明しました。
DateTime コンストラクタで使用可能の書式には、タイムゾーンによって解釈が変わる値と、そうでない値があります。now はタイムゾーンによらず一定のUNIX時間が生成されます。一方で、 today や日時を指定した場合はタイムゾーンによってUNIX時間が変わります。
DateTime コンストラクタの第1引数にタイムゾーンが含まれる場合、第2引数は無視されます。
setTimeZone() で後からタイムゾーンを変更してもUNIX時間に変化はありません。
format() で出力される年月日時分秒はタイムゾーンの影響を受けます。
DateTime の大小比較は、UNIX時間を比較した結果と同じになります。設定されているタイムゾーンは関係ありません。