【PHP】Carbonで月末日付をsubMonthしたら死んだ話

PHPerからしたらありふれすぎた話だけど、2021/07/31の1ヶ月前をCarbonで取得しようとして死んだ。

なにがおきたか

こうすると

$date = new Carbon('20210731')->subMonth(1);
echo($date);

こうなる

2021-07-01 00:00:00

2021-06-30 00:00:00じゃないの?

なぜか

CarbonはDateTimeのラッパー

7/31から1ヶ月減算すると6/31になるが、6/31という日付は存在しない。
なので、6月の月末日の6/30と6/31の差分を計算し、6/30に日付を1日加算する。
結果として7/1になる。

という仕様らしい。

解決策

subMonthsNoOverflowを使う

$date = new Carbon('20210731');
echo($date->subMonthsNoOverflow(1));
2021-06-30 00:00:00
$date = new Carbon('20210731123005');
echo($date->subMonthsNoOverflow(1));
2021-06-30 12:30:05

補足

翌月版もある

$date = new Carbon('20210831123005');
echo($date->addMonthsNoOverflow(1));
2021-09-30 12:30:05

9/31は存在しないがオーバーフローさせずに9/30日になっている