想要使用 PHP 的 strtotime() 方法通过 +1 month / -1 month 获取下个月/上个月的年和月,遇到了和预期结果不符的问题。
观察下面的测试代码的输出结果发现问题原因:
public function actionTestDate()
{
$startTime = '20180101'; // string
$startTimeStamp = strtotime($startTime); // stamp
$endTime = '20180331'; // string
$endTimeStamp = strtotime($endTime); // stamp
$result = [];
for ($i = $startTimeStamp; $i <= $endTimeStamp; $i += 86400) {
$iDate = date('Y-m-d H:i:s', $i); // date
$iLastMonth = date('Y-m', strtotime('-1 month', $i)); // date
$iNextMonth = date('Y-m', strtotime('+1 month', $i)); // date
$result[] = $iDate . '----' . $iLastMonth . '----' . $iNextMonth;
}
return $result;
}
输出结果如下:
[
"2018-01-01 00:00:00----2017-12----2018-02",
"2018-01-02 00:00:00----2017-12----2018-02",
"2018-01-03 00:00:00----2017-12----2018-02",
"2018-01-04 00:00:00----2017-12----2018-02",
"2018-01-05 00:00:00----2017-12----2018-02",
"2018-01-06 00:00:00----2017-12----2018-02",
"2018-01-07 00:00:00----2017-12----2018-02",
"2018-01-08 00:00:00----2017-12----2018-02",
"2018-01-09 00:00:00----2017-12----2018-02",
"2018-01-10 00:00:00----2017-12----2018-02",
"2018-01-11 00:00:00----2017-12----2018-02",
"2018-01-12 00:00:00----2017-12----2018-02",
"2018-01-13 00:00:00----2017-12----2018-02",
"2018-01-14 00:00:00----2017-12----2018-02",
"2018-01-15 00:00:00----2017-12----2018-02",
"2018-01-16 00:00:00----2017-12----2018-02",
"2018-01-17 00:00:00----2017-12----2018-02",
"2018-01-18 00:00:00----2017-12----2018-02",
"2018-01-19 00:00:00----2017-12----2018-02",
"2018-01-20 00:00:00----2017-12----2018-02",
"2018-01-21 00:00:00----2017-12----2018-02",
"2018-01-22 00:00:00----2017-12----2018-02",
"2018-01-23 00:00:00----2017-12----2018-02",
"2018-01-24 00:00:00----2017-12----2018-02",
"2018-01-25 00:00:00----2017-12----2018-02",
"2018-01-26 00:00:00----2017-12----2018-02",
"2018-01-27 00:00:00----2017-12----2018-02",
"2018-01-28 00:00:00----2017-12----2018-02",
"2018-01-29 00:00:00----2017-12----2018-03",
"2018-01-30 00:00:00----2017-12----2018-03",
"2018-01-31 00:00:00----2017-12----2018-03",
"2018-02-01 00:00:00----2018-01----2018-03",
"2018-02-02 00:00:00----2018-01----2018-03",
"2018-02-03 00:00:00----2018-01----2018-03",
"2018-02-04 00:00:00----2018-01----2018-03",
"2018-02-05 00:00:00----2018-01----2018-03",
"2018-02-06 00:00:00----2018-01----2018-03",
"2018-02-07 00:00:00----2018-01----2018-03",
"2018-02-08 00:00:00----2018-01----2018-03",
"2018-02-09 00:00:00----2018-01----2018-03",
"2018-02-10 00:00:00----2018-01----2018-03",
"2018-02-11 00:00:00----2018-01----2018-03",
"2018-02-12 00:00:00----2018-01----2018-03",
"2018-02-13 00:00:00----2018-01----2018-03",
"2018-02-14 00:00:00----2018-01----2018-03",
"2018-02-15 00:00:00----2018-01----2018-03",
"2018-02-16 00:00:00----2018-01----2018-03",
"2018-02-17 00:00:00----2018-01----2018-03",
"2018-02-18 00:00:00----2018-01----2018-03",
"2018-02-19 00:00:00----2018-01----2018-03",
"2018-02-20 00:00:00----2018-01----2018-03",
"2018-02-21 00:00:00----2018-01----2018-03",
"2018-02-22 00:00:00----2018-01----2018-03",
"2018-02-23 00:00:00----2018-01----2018-03",
"2018-02-24 00:00:00----2018-01----2018-03",
"2018-02-25 00:00:00----2018-01----2018-03",
"2018-02-26 00:00:00----2018-01----2018-03",
"2018-02-27 00:00:00----2018-01----2018-03",
"2018-02-28 00:00:00----2018-01----2018-03",
"2018-03-01 00:00:00----2018-02----2018-04",
"2018-03-02 00:00:00----2018-02----2018-04",
"2018-03-03 00:00:00----2018-02----2018-04",
"2018-03-04 00:00:00----2018-02----2018-04",
"2018-03-05 00:00:00----2018-02----2018-04",
"2018-03-06 00:00:00----2018-02----2018-04",
"2018-03-07 00:00:00----2018-02----2018-04",
"2018-03-08 00:00:00----2018-02----2018-04",
"2018-03-09 00:00:00----2018-02----2018-04",
"2018-03-10 00:00:00----2018-02----2018-04",
"2018-03-11 00:00:00----2018-02----2018-04",
"2018-03-12 00:00:00----2018-02----2018-04",
"2018-03-13 00:00:00----2018-02----2018-04",
"2018-03-14 00:00:00----2018-02----2018-04",
"2018-03-15 00:00:00----2018-02----2018-04",
"2018-03-16 00:00:00----2018-02----2018-04",
"2018-03-17 00:00:00----2018-02----2018-04",
"2018-03-18 00:00:00----2018-02----2018-04",
"2018-03-19 00:00:00----2018-02----2018-04",
"2018-03-20 00:00:00----2018-02----2018-04",
"2018-03-21 00:00:00----2018-02----2018-04",
"2018-03-22 00:00:00----2018-02----2018-04",
"2018-03-23 00:00:00----2018-02----2018-04",
"2018-03-24 00:00:00----2018-02----2018-04",
"2018-03-25 00:00:00----2018-02----2018-04",
"2018-03-26 00:00:00----2018-02----2018-04",
"2018-03-27 00:00:00----2018-02----2018-04",
"2018-03-28 00:00:00----2018-02----2018-04",
"2018-03-29 00:00:00----2018-03----2018-04",
"2018-03-30 00:00:00----2018-03----2018-04",
"2018-03-31 00:00:00----2018-03----2018-05"
]
从输出结果分析发现:
2018 年 2 月份有 28 天,那么 2018 年 1 月份中 28 日 之后的日期的下个月是不符合预期的(2018-01-29、2018-01-30、2018-01-31);
2018 年 2 月份有 28 天,那么 2018 年 3 月份中 28 日 之后的日期的上个月是不符合预期的(2018-03-29、2018-03-30、2018-03-31);
......
结论: 本月天数大于上月天数或本月天数大于下月天数均会出现类似问题。伪代码如下:
if (本月天数 > 下月天数) {
本月的最后 (本月天数 - 下月天数) 的下月日期不符合预期;
}
举例:
本月为 1 月, 31 天;下月为 2 月, 28 天;1 月的最后 3 天的下月日期不符合预期;
if (本月天数 > 上月天数) {
本月的最后 (本月天数 - 上月天数) 的上月日期不符合预期;
}
举例:
本月为 3 月, 31 天;上月为 2 月, 28 天;3 月的最后 3 天的上月日期不符合预期;
解决方案:
先获取到本月的第一天,然后进行 +1 month、-1 month 的操作。
public function actionTestDate()
{
$startTime = '20180101'; // string
$startTimeStamp = strtotime($startTime); // stamp
$endTime = '20180331'; // string
$endTimeStamp = strtotime($endTime); // stamp
$result = [];
for ($i = $startTimeStamp; $i <= $endTimeStamp; $i += 86400) {
$iDate = date('Y-m-d H:i:s', $i); // date
$iFirstDay = date('Y-m-01', $i); // date
$iLastMonth = date('Y-m', strtotime('-1 month', strtotime($iFirstDay))); // date
$iNextMonth = date('Y-m', strtotime('+1 month', strtotime($iFirstDay))); // date
$result[] = $iDate . '----' . $iLastMonth . '----' . $iNextMonth;
}
return $result;
}