概要
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;
date_default_timezone_set('Asia/Tokyo');
$from = FrozenTime::create(2021, 1, 1);
$to = FrozenTime::create(2021, 3, 1);
$result1 = $from->diffInMonths($to);
$result2 = $from->diffInMonthsIgnoreTimezone($to);
FrozenDate を使った場合
ところが FrozenDate を使うと、どうにも 1 が返ってきてしまう。
<?php
use Cake\I18n\FrozenDate;
date_default_timezone_set('Asia/Tokyo');
$from = FrozenDate::create(2021, 1, 1);
$to = FrozenDate::create(2021, 3, 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;
date_default_timezone_set('Asia/Tokyo');
$from = FrozenDate::create(2021, 1, 1);
$to = FrozenDate::create(2021, 3, 1);
$result = $from->diffInMonthsIgnoreTimezone($to);
宣伝
今回の検証にあたって、以前作った Cake 用の Docker Image に PHP8.1 版を追加しておいたので、よかったら使ってみてください。
参考
dateInMonths の罠については↓らへんの記事が参考になった。