strtotime () 帶來的 bug

wacho發表於2020-04-01

經常會有人被strtotime結合-1 month, +1 month, next month的時候搞得很困惑, 然後就會覺得這個函式有點不那麼靠譜, 動不動就出問題. 用的時候就會很慌…

比如:

date("Y-m-d",strtotime("+1 month"))

當前時間是 3 .31,加一個月應該是 4.31,但是由於4 月沒有 31 號,在對日期規範化後得到的就是 5 月 1 號了。

也就是說, 只要涉及到大小月的最後一天, 都可能會有這個迷惑, 我們也可以很輕鬆的驗證類似的其他月份, 印證這個結論:

var_dump(date("Y-m-d", strtotime("-1 month", strtotime("2017-03-31"))));
//輸出2017-03-03
var_dump(date("Y-m-d", strtotime("+1 month", strtotime("2017-08-31"))));
//輸出2017-10-01
var_dump(date("Y-m-d", strtotime("next month", strtotime("2017-01-31"))));
//輸出2017-03-03
var_dump(date("Y-m-d", strtotime("last month", strtotime("2017-03-31"))));
//輸出2017-03-03

那怎麼辦呢?

從PHP5.3開始呢, date新增了一系列修正短語, 來明確這個問題, 那就是”first day of” 和 “last day of”, 也就是你可以限定好不要讓date自動”規範化”:

var_dump(date("Y-m-d", strtotime("last day of -1 month", strtotime("2017-03-31"))));
//輸出2017-02-28
var_dump(date("Y-m-d", strtotime("first day of +1 month", strtotime("2017-08-31"))));
////輸出2017-09-01
var_dump(date("Y-m-d", strtotime("first day of next month", strtotime("2017-01-31"))));
////輸出2017-02-01
var_dump(date("Y-m-d", strtotime("last day of last month", strtotime("2017-03-31"))));
////輸出2017-02-28

還有一種方法,使用DateTime:

$d = new DateTime( '2020-03-31' );
$d->modify( 'first day of next month' );
echo $d->format("Y-m-d"); 

同樣的也是支援first day of 以及*last day of *,最後 format 轉換成我們想要的格式。

使用第三方類Carbon

我們開啟原始碼發現也是繼承了*DateTime *

image-20200401134013806

它有個方法,可以直接加一個月,addMonthNoOverflow,點進去看原始碼;

/**
     * Add a month with no overflow to the instance
     *
     * @param int $value
     *
     * @return static
     */
    public function addMonthNoOverflow($value = 1)
    {
        return $this->addMonthsNoOverflow($value);
    }

再點進去發現,他其實也是利用的 last day of

 /**
     * Add months without overflowing to the instance. Positive $value
     * travels forward while negative $value travels into the past.
     *
     * @param int $value
     *
     * @return static
     */
    public function addMonthsNoOverflow($value)
    {
        $day = $this->day;

        $this->modify((int) $value.' month');

        if ($day !== $this->day) {
            $this->modify('last day of previous month');
        }

        return $this;
    }
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章