PHP7 で CakePHP4 の FrozenDate を使うと、月差分が正しく判定されない場合がある #php #cakephp

概要

PHP8.1 未満の環境における CakePHP の FrozenDate は、 UTC でない Timezone を使った場合に、日付差分が正しく取れないケースがある。

FrozenTime を使うようにするか、 PHP8.1 以降にバージョンを上げると解決する。

※タイトルでは PHP7 としているけど、 8.0 でもだめなはず。

PHP8.1未満で比較する場合

環境は PHP7.4 , CakePHP は 4.2.8 で試す。

たとえば 2021年1月1日から3月1日までの差分を求めるとする。

期待値は当然 2 だ。

$ php -v
PHP 7.4.24 (cli) (built: Sep 23 2021 22:49:50) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.24, Copyright (c), by Zend Technologies
    with Xdebug v2.9.8, Copyright (c) 2002-2020, by Derick Rethans
$ bin/cake version
4.2.8

FrozenTime を使った場合

以前からあった diffInMonths は TimeZone の問題を抱えていたが、diffInMonthsIgnoreTimezone が提供されるようになったことで、期待通りの結果が得られるようになった。

github.com

<?php
use Cake\I18n\FrozenTime;

// UTC 以外にする
date_default_timezone_set('Asia/Tokyo');

$from = FrozenTime::create(2021, 1, 1);
$to = FrozenTime::create(2021, 3, 1);

// UTC 以外だと 1 が返ってきてしまうのでだめ
$result1 = $from->diffInMonths($to);

// 期待通り!
$result2 = $from->diffInMonthsIgnoreTimezone($to);

FrozenDate を使った場合

ところが FrozenDate を使うと、どうにも 1 が返ってきてしまう。

<?php
use Cake\I18n\FrozenDate;

// UTC 以外にする
date_default_timezone_set('Asia/Tokyo');

$from = FrozenDate::create(2021, 1, 1);
$to = FrozenDate::create(2021, 3, 1);

// IgnoreTimezone しても 1 が返ってきてしまう!
$result = $from->diffInMonthsIgnoreTimezone($to);

原因

前述の Issue にコメントされているのだが、どうやらそもそも DateTime::diff に問題があったようで、バグ報告されている。

このバグは既に対応済で、 PHP8.1 のリリースに含まれている。

PHP8.1で比較する場合

環境は PHP8.1 , CakePHP は 4.3.2 で試す。

$ php -v  
PHP 8.1.0 (cli) (built: Nov 30 2021 06:10:58) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.0, Copyright (c) Zend Technologies
$ bin/cake version
4.3.2

この環境で試すと、期待通りの結果が得られる。

(コードは同じもの)

<?php
use Cake\I18n\FrozenDate;

// UTC 以外にする
date_default_timezone_set('Asia/Tokyo');

$from = FrozenDate::create(2021, 1, 1);
$to = FrozenDate::create(2021, 3, 1);

// 無事 2 が返ってくるようになった
$result = $from->diffInMonthsIgnoreTimezone($to);

宣伝

今回の検証にあたって、以前作った Cake 用の Docker Image に PHP8.1 版を追加しておいたので、よかったら使ってみてください。

参考

dateInMonths の罠については↓らへんの記事が参考になった。