strtotime() 是 PHP 核心日期時間處理函式,自 PHP 4 起內建。它能將英文文字日期描述解析為 Unix 時間戳。本文從原始碼層面剖析其實作機制,並解釋為何 strtotime("-1 month") 在特定日期會產生非預期結果。
strtotime("JAN") 與 strtotime("January") 效果相同:回傳指定月份的今天;若該月無此日(如 1 月 31 日往 2 月),則順延至下月 3 號。
echo date("Y-m-d H:i:s", strtotime("feb", strtotime("2011-03-31")));
// 輸出:2011-03-03 00:00:00
原理:內部直接修改 timelib_time 結構體的 m 欄位,不重新計算日數上限。
time->d = 1)echo date("Y-m-d H:i:s", strtotime("last day", strtotime("2000-02-01")));
// 2000-02-29 00:00:00(閏年)
參數範圍 0~24,超出會報錯。
位於 ext/date/php_date.c 的 PHP_FUNCTION(strtotime):
(string time, int now) 或單參數 (string time)timelib_time 結構體 now 作為基準時間timelib_strtotime() 進行詞法解析timelib_fill_holes() 補齊缺失欄位timelib_update_ts() 計算最終時間戳使用 re2c 產生的解析器(parse_date.re),針對輸入字串進行模式匹配:
'yesterday'
{
DEBUG_OUTPUT("yesterday");
TIMELIB_INIT;
TIMELIB_HAVE_RELATIVE();
TIMELIB_UNHAVE_TIME();
s->time->relative.d = -1; // 相對天數 -1
TIMELIB_DEINIT;
return TIMELIB_RELATIVE;
}
關鍵結構體:
Scanner:掃描器狀態timelib_time:儲存年月日時分秒、時區、相對時間等timelib_rel_time:相對時間量(年、月、日、時、分、秒、週幾等)輸入 "-1 month" 會匹配 relative 規則:
relnumber = ([+-]*[ \t]*[0-9]+);
reltextunit = 'month' ...;
relative = relnumber space? (reltextunit | 'week');
識別出 amount = -1、unit = TIMELIB_MONTH,執行:
case TIMELIB_MONTH:
s->time->relative.m += amount * relunit->multiplier; // m -= 1
break;
僅修改相對月份,不處理日期溢位。
timelib_fill_holes() 補齊缺失欄位時,發現 2 月無 31 日,自動向後推算至 3 月 3 日。這是 設計行為而非 Bug——PHP 選擇保留「日」不變,由日曆系統自然溢位。
需求「上個月同一天」應改用:
// 方法 1:先取上月 1 號,再加天數
$firstDayThisMonth = strtotime("first day of this month");
$lastDayPrevMonth = strtotime("last day of previous month", $firstDayThisMonth);
// 方法 2:DateTime 物件(PHP 5.3+)
$dt = new DateTime("2011-03-31");
$dt->modify("last day of previous month");
echo $dt->format("Y-m-d"); // 2011-02-28
strtotime() 以 re2c 詞法解析器將自然語言轉為結構化相對時間,再由 timelib 計算時間戳。其「月份運算不保護日期」的特性源於底層直接操作 timelib_rel_time.m,開發者需自行處理月末邊界情況,建議改用 DateTime 類別獲得更直觀的日期運算。