PHP Carbon 的兩個坑


#PHP#Laravel#Carbon#addMonth#subMonth#addMonthNoOverflow#subMonthNoOverflow#floorMonth

Carbon 是 PHP/Laravel 非常實用的一個時間套件,但使用時有遇到神奇的坑。

addMonth、subMonth


當想拿到下個月的月份時會很直覺的寫成

$nextMonth = Carbon::parse('2022-01-31')->addMonth()->month;
dd($nextMonth);

但上述程式碼執行後會得到 3,WTF 一月的下個月是三月?
原因是因為他底層是使用 strtotime,2022-01-31 加一個月就是 2022-02-31,由於二月只有 28 天所以會 Overflow,變成 02/28 加三天 03/03,同理 sub 也會出現 03/31 的上個月是 3 月的問題。

解決辦法

在純 PHP 使用 strtotime 的話可以使用 first day of 解決,在 Carbon 中已經幫你處理好了,使用 NoOverflow 系列 (addMonthNoOverflowsubMonthNoOverflow) 就可避免天數 Overflow。

dump(Carbon::parse('2022-01-31')->addMonthNoOverflow()->month); // 2
dump(Carbon::parse('2022-03-31')->subMonthNoOverflow()->month); // 2

floorMonth


floorMonth 理應是以每個月當區間取 floor ,不設定 precision 的話就是類似 startOfMonth 的功能 取得當月第一天的凌晨,但卻在大月最後一天時會異常,取得到的卻是下個月的第一天凌晨。
不過這 bug 在 2.58.0 版已經被修正了:https://github.com/briannesbitt/Carbon/pull/2583/commits/3ff0d04b81729647f108435cac7ce05e03b6b979

for ($i = 1; $i <= 12; $i++) {
	$dt = Carbon::parse("2022-$i-01")->endOfMonth()->floorMonth();
	dump($dt->toDateString());
}

output

"2022-02-01"
"2022-02-01"
"2022-04-01"
"2022-04-01"
"2022-06-01"
"2022-06-01"
"2022-08-01"
"2022-09-01"
"2022-09-01"
"2022-11-01"
"2022-11-01"
"2023-01-01"