來自PHP的strtotime函數的坑
問題描述
今天,我手邊某個外包的Case收到客戶的友善回報,當用戶希望將某個日期的預約時間移動到下個月或者提前一個月時,有時候會發生跟預期的結果的不同,最終將問題定位在strtotime這個函數上。
問題重現
$reservation_date = "2019-09-05";
var_dump(date("Y-m-d", strtotime("-1 month", strtotime($reservation_date)))); //2019-08-05-如預期
$reservation_date = "2019-09-05";
var_dump(date("Y-m-d", strtotime("+1 month", strtotime($reservation_date)))); //2019-10-05-如預期
$reservation_date = "2019-08-31";
var_dump(date("Y-m-d", strtotime("-1 month", strtotime($reservation_date)))); //2019-07-31-如預期
$reservation_date = "2019-07-31";
var_dump(date("Y-m-d", strtotime("-1 month", strtotime($reservation_date)))); //2019-07-01
$reservation_date = "2019-03-31";
var_dump(date("Y-m-d", strtotime("-1 month", strtotime($reservation_date)))); //2019-03-03
$reservation_date = "2016-03-31";
var_dump(date("Y-m-d", strtotime("-1 month", strtotime($reservation_date)))); //2016-03-02
問題原因
從上面重現的程式碼來看,主要的問題是發生在月底最後一天,且是發生在大小月(月份天數不同)的情境下,會發生這個問題原因主要是strtotime內部時間計算邏輯是以對應日的方式去處理。
以上面問題重現的第四個錯誤的例子來說。
$reservation_date = "2019-07-31";
var_dump(date("Y-m-d", strtotime("-1 month", strtotime($reservation_date)))); //2019-07-01
對應日,例如07-31減去一個月後的對應日是06-31,但是由於6月份沒有31日,所以進行日期規範化後變成07-01。
有待驗證的想法
其實我更覺得,可能會像是,會把差距的天數加在下一個月份。 例如,上面的例子由於6月沒有31日只有30日,所以
31-30 = 1,然後把這個數字加進下個月的裡,從0開始加,就變得是7月0日+1結果是7月1日。
根據上面的想法,看第5個錯誤的例子。
$reservation_date = "2019-03-31";
var_dump(date("Y-m-d", strtotime("-1 month", strtotime($reservation_date)))); //2019-03-03
3月31的前一個月的對應日為2月31日,但是由於2月只有28天,所以31-28 = 3,把數字加入到下個月裡,變成3月0日+3,結果是3月3日。
接著,我們繼續看第6個錯誤的例子。
$reservation_date = "2016-03-31";
var_dump(date("Y-m-d", strtotime("-1 month", strtotime($reservation_date)))); //2016-03-02
3月2日,這邊主要是閏年的關係,2016年2月有29天。
解決方式
- 使用修飾短語
last day of - 用時間戳記處理