/

PHP 月份加减问题

看现象

var_dump(date("Y-m-d", strtotime("+1 month", strtotime("2020-07-31"))));
// string(10) "2020-08-31" 符合预期

var_dump(date("Y-m-d", strtotime("+1 month", strtotime("2020-05-31"))));
// string(10) "2020-07-01" 不符合预期,预期 2020-06-30

var_dump(date("Y-m-d", strtotime("-1 month", strtotime("2020-02-29"))));
// string(10) "2020-01-29" 符合预期

var_dump(date("Y-m-d", strtotime("-1 month", strtotime("2020-03-31"))));
// string(10) "2020-03-02" 不符合预期,预期 2020-02-29


// Carbon\Carbon
Carbon::parse("2020-07-31")->addMonth()->toDateString();
// "2020-08-31"
Carbon::parse("2020-05-31")->addMonth()->toDateString();
// "2020-07-01"
Carbon::parse("2020-02-29")->subMonth()->toDateString();
// "2020-01-29"
Carbon::parse("2020-03-31")->subMonth()->toDateString();
// "2020-03-02"

// 结果与 strtotime 一致。

原因

var_dump(date("Y-m-d", strtotime("+1 month", strtotime("2020-05-31"))));
// string(10) "2020-07-01"

date 内部的处理逻辑:

  1. 2020-05-31+1 month 也就是 2020-06-31
  2. 再做日期规范化,因为没有 06-31,所以 06-31 就等于了 07-01
var_dump(date("Y-m-d", strtotime("2020-06-31")));
// string(10) "2017-07-01"

var_dump(date("Y-m-d", strtotime("next month", strtotime("2017-01-31"))));
// string(10) "2017-03-03"

var_dump(date("Y-m-d", strtotime("last month", strtotime("2017-03-31"))));
// string(10) "2017-03-03"

解决方案

var_dump(date("Y-m-d", strtotime("last day of -1 month", strtotime("2017-03-31"))));
// string(10) "2017-02-28"

var_dump(date("Y-m-d", strtotime("first day of +1 month", strtotime("2017-08-31"))));
// string(10) "2017-09-01"

// 但要注意短语的含义:
var_dump(date("Y-m-d", strtotime("last day of -1 month", strtotime("2017-03-01"))));
// string(10) "2017-02-28"

如果使用 Carbon\Carbon 可以用 subMonthNoOverflowaddMonthNoOverflow 防止进位:

Carbon::parse('2020-03-31')->subMonthNoOverflow()->toDateString();
// "2020-02-29"

Carbon::parse("2020-05-31")->addMonthNoOverflow()->toDateString();
// "2020-06-30"

References

– EOF –