> php -r "echo date('Ymd', strtotime('-1 month', strtotime('20130731')));"
20130701 // 錯誤!應為 20130630
> php -r "echo date('Ymd', strtotime('-1 month', strtotime('20130801')));"
20130701 // 正確
原因:strtotime('-1 month') 簡單減少月份數,不處理月末邊界。
<?php
function lastMonthSameDay($timestamp) {
return mktime(
date('H', $timestamp),
date('i', $timestamp),
date('s', $timestamp),
date('n', $timestamp) - 1, // 月份 -1
date('j', $timestamp), // 保持同一天
date('Y', $timestamp)
);
}
// 測試
echo date('Y-m-d', lastMonthSameDay(strtotime('2013-07-31'))); // 2013-07-01 (PHP自動處理溢出)
// 註:PHP mktime 會自動將 6月31日 修正為 7月1日
<?php
function lastMonthLastDay($timestamp) {
// 當月 0 日 = 上個月最後一天
return mktime(0, 0, 0, date('n', $timestamp), 0, date('Y', $timestamp));
}
echo date('Y-m-d', lastMonthLastDay(strtotime('2013-07-15'))); // 2013-06-30
echo date('Y-m-d', lastMonthLastDay(strtotime('2013-03-15'))); // 2013-02-28 (非閏年)
echo date('Y-m-d', lastMonthLastDay(strtotime('2012-03-15'))); // 2012-02-29 (閏年)
<?php
/**
* 計算上個月同一天,若上個月無此日則返回上個月最後一天
*/
function lastMonthToday($timestamp) {
// 上個月 0 點時間戳
$lastMonthTs = mktime(
date('G', $timestamp),
date('i', $timestamp),
date('s', $timestamp),
date('n', $timestamp),
0, // 關鍵:第 0 天 = 上個月最後一天
date('Y', $timestamp)
);
$lastMonthDays = date('t', $lastMonthTs); // 上個月總天數
$currentDay = date('j', $timestamp); // 當前幾號
if ($lastMonthDays < $currentDay) {
// 上個月沒有這一天,返回月末
return date('Y-m-t H:i:s', $lastMonthTs);
}
// 有這一天,返回同一天
return date('Y-m-' . $currentDay . ' H:i:s', $timestamp);
}
// 測試
echo lastMonthToday(strtotime('2013-07-31 12:00:00')); // 2013-06-30 12:00:00
echo lastMonthToday(strtotime('2013-07-15 12:00:00')); // 2013-06-15 12:00:00
echo lastMonthToday(strtotime('2012-03-31 12:00:00')); // 2012-02-29 12:00:00 (閏年)
echo lastMonthToday(strtotime('2013-03-31 12:00:00')); // 2013-02-28 12:00:00 (非閏年)
<?php
$date = new DateTime('2013-07-31');
$date->modify('first day of last month'); // 2013-06-01
$date->modify('last day of last month'); // 2013-06-30
// 或者
$date = new DateTime('2013-07-31');
$interval = new DateInterval('P1M');
$date->sub($interval); // 2013-07-01 (同樣有 Bug)
// 正確做法:先設為 1 號再減月
$date = new DateTime('2013-07-31');
$date->setDate($date->format('Y'), $date->format('m'), 1);
$date->sub(new DateInterval('P1M'));
$date->setDate($date->format('Y'), $date->format('m'), min(31, $date->format('t')));
| 輸入日期 | 預期上月同天 | strtotime 結果 | 方案三結果 |
|---|---|---|---|
| 2013-01-31 | 2012-12-31 | 2013-01-01 ❌ | 2012-12-31 ✅ |
| 2013-03-31 | 2013-02-28 | 2013-03-03 ❌ | 2013-02-28 ✅ |
| 2012-03-31 | 2012-02-29 | 2012-03-02 ❌ | 2012-02-29 ✅ |
| 2013-07-15 | 2013-06-15 | 2013-06-15 ✅ | 2013-06-15 ✅ |
| 2013-08-31 | 2013-07-31 | 2013-07-31 ✅ | 2013-07-31 ✅ |