CakePHP 4x: JST の Chronos インスタンスで diffInMonths すると正しい結果が得られない (回避方法も記載)

(2021/01/18 追記)

  • 次回リリースされるバージョンの Chronos では問題が解消されるようです
    • #283 にて修正済み
    • UTC 以外の場合は diffInMonthsIgnoreTimezone() を使えばok

バージョン

what

すでに公式に issue が上がってるが、今回調べた事をメモ

issue に書いてある内容のまとめ

下記の場合に diffInMonths が正しい結果を返さない

  • App.defaultTimezoneUTC 以外の場合に、タイムゾーンを指定せず、new Chronos() して diffInMonths()
  • タイムゾーンに UCT 以外を指定して new Chronos() して diffInMonths()

原因

  • Chronos 2.x 以降、Date のタイムゾーンは常にサーバのタイムゾーン (App.defaultTimezone) に固定される

  • Date (Chronos) は内部で、Datetime (PHP) の diff() を使用している

  • PHPdiff() の前に、タイムゾーンUTCに変換している

  • 上記により

    • App.defaultTimezoneUTC 以外の場合に、タイムゾーンを指定せず(もしくは UTC 以外を指定して) new Chronos() して diffInMonths() すると意図した結果が得られない

回避方法

  • diffInMonths する前に、UTCで日時を偽装する
$chronosJST = (Chronos()::now)->setTimezone('+09:00');

// diffInMonths する前に、UTCで日時を偽装
$chronosFake = (Chronos::create(
    (int)$chronosJST->format('Y'),
    (int)$chronosJST->format('m'),
    (int)$chronosJST->format('d'),
    (int)$chronosJST->format('H'),
    (int)$chronosJST->format('i'),
    (int)$chronosJST->format('s'),
    (int)$chronosJST->format('u'),
    'UTC'
));

$chronosFake->diffInMonths(...);