Why not login to Qiita and try out its useful features?

We'll deliver articles that match you.

You can read useful information later.

0
0

DateTime::diff()は時間差判定には(ほぼ)使えない

Last updated at Posted at 2024-10-05

概要

phpでふたつの日時の差分を判定するとき、DateTime::diff()は基本的に不適切である。

不適切さの詳細

  • ふたつの日時が「○分以内だったら」「○秒以上だったら」という比較には使えない。
  • DateTime::diff()の戻り値であるDateIntervalは、「○年○か月○日○時間○分○秒」というデータの持ち方をしている。
  • たとえば「差分が5秒以上か」を判定したいとき、「60秒差」だったら、DateIntervalは「1分0秒(秒はゼロ)」なので、false判定になってしまう。

1. 例

1-1. サンプルコード

$dt1 = new DateTime('2001-01-01 00:00:00');
$dt2 = new DateTime('2002-03-04 05:06:07'); // 1年2か月3日5時間6分7秒後
$diff = $dt1->diff($dt2);

// デバッグ用関数。DateIntervalの指定プロパティを出力する。
function test(DateInterval $di, string $propName, string $comment) {
    echo $propName . ' = ' . $di->$propName . ' (' . $comment . ')' . PHP_EOL;
}

test($diff, 'y', '年');
test($diff, 'm', '月。1年2か月後なので14を所望する。');
test($diff, 'd', '日。14か月と3日後なので368を所望する。');
test($diff, 'h', '時。368*24+5=44,160を所望する。');
test($diff, 'i', '分。44160*60+6=2,649,606を所望する。');
test($diff, 's', '秒。2,649,606*60+7=158,976,367を所望する。');

1-1. サンプルコード結果

y = 1 (年)
m = 2 (月。1年2か月後なので14を所望する。)
d = 3 (日。14か月と3日後なので368を所望する。)
h = 5 (時。368*24+5=44,160を所望する。)
i = 6 (分。44160*60+6=2,649,606を所望する。)
s = 7 (秒。2,649,606*60+7=158,976,367を所望する。)

1-3. サンプル結果をうけた所感

「○年○か月○日○時間○分○秒」という表現を使いたいことがあれば役立つかもしれない。

2. 例外

2-1. 「年数」の評価は、最大単位なのでそのまま使える

たとえば「分」のプロパティmは、59分後なら59だが、60分後は上位単位であるhに吸収され、0になってしまう。
しかし「年」は、上位単位が存在しないので、そのようなことは起こらない。

2-2. 「日数」の評価は、プロパティ「days」でいける

daysというプロパティがある。
これは時間差判定のうえで期待通りの挙動をする。

2-2-1. サンプルコード

// デバッグ用関数。DateIntervalの指定プロパティを出力する。
function test(DateInterval $di, string $propName, string $comment) {
    echo $propName . ' = ' . $di->$propName . ' (' . $comment . ')' . PHP_EOL;
}

$dt3 = new DateTime('2001-01-01 12:00:00'); // 基準日時(12時)
$dt4 = new DateTime('2001-01-02 11:59:59'); // 翌日の11:59:59
$dt5 = new DateTime('2001-01-02 12:00:00'); // 翌日の12:00:00
$dt6 = new DateTime('2002-01-01 11:59:59'); // 翌年同日の11:59:59
$dt7 = new DateTime('2002-01-01 12:00:00'); // 翌年同日の12:00:00

$diff3_4 = $dt3->diff($dt4);
$diff3_5 = $dt3->diff($dt5);
$diff3_6 = $dt3->diff($dt6);
$diff3_7 = $dt3->diff($dt7);

test($diff3_4, 'days', '12:00と翌日11:59:59のdays');
test($diff3_5, 'days', '12:00と翌日12:00:00のdays');
test($diff3_6, 'days', '1/1 12:00と翌年同日11:59:59のdays');
test($diff3_7, 'days', '1/1 12:00と翌年同日12:00:00のdays');

2-2-2. サンプルコード結果

days = 0 (12:00と翌日11:59:59のdays)
days = 1 (12:00と翌日12:00:00のdays)
days = 364 (1/1 12:00と翌年同日11:59:59のdays)
days = 365 (1/1 12:00と翌年同日12:00:00のdays)

サンプル結果をうけた所感

日数の比較は可能。
seconds, minutes, hours, months, の実装を待ちたい。
せめてsecondsだけでも。
ていうかなんでdaysだけなんだよ。ひとつだけならsecondsにしてよ。

3. DateTime::diff()がだめなら、何を使えばいいのか

3-1. 差分そのものが必要なとき

私はDateTime::format('U') (unixtime)の差分を使ってます。

3-1-1. サンプル

差分そのものを用いて差分比較をするサンプルです。

function isDiffMoreThanSeconds(string $s1, string $s2, int $sec) {
    
    // 引数をDateTime型に
    $d1 = new DateTime($s1);
    $d2 = new DateTime($s2);

    // それぞれのunixtimeを取得
    $u1 = $d1->format('U');
    $u2 = $d2->format('U');
    
    // 差分
    $diffSecond = $u2 - $u1;
    
    $isMoreThan = $diffSecond >= $sec ? true : false;
    
    echo sprintf("%s, %s, 差は%s秒以上か? → %s\n", $s1, $s2, $sec, ($isMoreThan?'true':'false'));
}

isDiffMoreThanSeconds('2024-01-01 00:00:00', '2024-01-01 00:00:04', 5);
isDiffMoreThanSeconds('2024-01-01 00:00:00', '2024-01-01 00:00:05', 5);
isDiffMoreThanSeconds('2024-01-01 00:00:00', '2024-01-01 23:59:59', 86400); // 1日は86400秒
isDiffMoreThanSeconds('2024-01-01 00:00:00', '2024-01-02 00:00:00', 86400);

3-1-2. サンプル結果

2024-01-01 00:00:00, 2024-01-01 00:00:04, 差は5秒以上か? → false
2024-01-01 00:00:00, 2024-01-01 00:00:05, 差は5秒以上か? → true
2024-01-01 00:00:00, 2024-01-01 23:59:59, 差は86400秒以上か? → false
2024-01-01 00:00:00, 2024-01-02 00:00:00, 差は86400秒以上か? → true

3-1-3. サンプル結果をうけた所感

レガシー感がありますが分かりやすいのでは

3-2. (差分の値そのものは必要ではなく)差が○○以上なら、○○以下なら、という判定をしたいとき

比較したい2つのうちの小さい方にDateTime:add(DateTimeInterval) をしたうえで比較する

3-2-1. サンプル

function isDiffMoreThanSeconds(string $s1, string $s2, int $sec) {
    
    // 引数をDateTime型に
    $d1 = new DateTime($s1);
    $d2 = new DateTime($s2);

    // $d1を$sec秒プラスする
    $d1->add(new DateInterval('PT' . $sec . 'S'));
    
    $isMoreThan = ($d1 <= $d2) ? true : false;
    
    echo sprintf("%s, %s, 差は%s秒以上か? → %s\n", $s1, $s2, $sec, ($isMoreThan?'true':'false'));
}

3-2-2. サンプル結果

3-3-1の isDiffMoreThanSeconds を差し替えると同じ結果になるので省略します。

3-2-3. サンプル所感

unixtimeを使わない方法を探したらこうなりました。
けどあまり直感的ではないと思ってます。
「わかりやすさ」だいじ。
慣れの問題かもですが。

4. この記事を書いた理由などポエム

DateTime::diffで日時差分判定するコードが立て続けにプルリクエストに上がってきたから。
あと雑に調べたら、検索上位にそんなサンプルを書いたページがちらほらしたから。

もっとも、公式 https://www.php.net/manual/ja/class.dateinterval.php を見て、そういう用途に使えると期待するのは仕方ないかなと思います。
そして軽い挙動確認では、秒単位でテストするときに分以上の差分などは考慮せず、バグが埋め込まれるという。
プロパティがy,m,d,h,i,sと、まるでdate_formatなので、そこで違和感に気づけたのが幸いでした。

DateInterval オブジェクトが保持している情報は、 ある date/time オブジェクトから別の date/time オブジェクトに情報を移す手順です。

とあるので、 DateTime::add DateTime::sub に用いる前提のクラスということなのでしょう。きっと。
そのわりには days プロパティなんて謎なものもありますが。
なんだかんだやっぱり seconds プロパティを実装してほしいです。

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Comments

oswe99489
@oswe99489

個人的には
$d1->add(new DateInterval('PT' . $sec . 'S')); より
$d1->modify("+{$sec} second"); のほうが分かりやすいかな。

1

Let's comment your feelings that are more than good

Qiita Conference 2024 Autumn will be held!: 11/14(Thu) - 11/15(Fri)

Qiita Conference is the largest tech conference in Qiita!

Keynote Speaker

Takahiro Anno, Masaki Fujimoto, Yukihiro Matsumoto(Matz)

View event details

Being held Article posting campaign

0
0

Login to continue?

Login or Sign up with social account

Login or Sign up with your email address