PHP 日期時間處理核心在於 strtotime() 強大的相對時間解析能力與 DateTime 物件導向介面。本文匯總高頻場景最佳實踐,修正常見誤區。
| 工具 | 優勢 | 適用場景 |
|---|---|---|
| strtotime() | 字串解析極強、相對格式豐富 | 快速單次運算、模板輸出 |
| DateTime | 物件導向、時區支援、鏈式調用 | 複雜業務邏輯、國際化、持久化 |
| DateTimeImmutable | 不可變、線程安全 | 函數式風格、避免副作用 |
// 基準時間
$base = '2024-01-15 10:30:00';
// 常用相對格式
$cases = [
'30 days' => '+30 days',
'1 month' => '+1 month',
'3 months ago' => '-3 months',
'next Monday' => 'next Monday',
'last day of March' => 'last day of March 2024',
'first day of' => 'first day of this month',
'yesterday' => 'yesterday',
'tomorrow noon' => 'tomorrow 12:00',
];
foreach ($cases as $desc => $rel) {
$ts = strtotime($rel, strtotime($base));
echo "{$desc}: " . date('Y-m-d H:i:s', $ts) . "\n";
}
// ⚠️ 陷阱:月份邊界
echo date('Y-m-d', strtotime('+1 month', strtotime('2024-01-31'))); // 2024-03-02 (非 2-31)
// 修正:使用 'first day of +1 month' 或 DateTime
$dt = new DateTime('2024-01-31');
$dt->modify('first day of +1 month');
echo $dt->format('Y-m-d'); // 2024-02-01
$start = '2024-01-01';
$end = '2024-03-15';
// 方式一:DateTime::diff() 最準確(自動處理閏年、月份長短)
$dt1 = new DateTime($start);
$dt2 = new DateTime($end);
$diff = $dt1->diff($dt2);
echo "相差 {$diff->y} 年 {$diff->m} 月 {$diff->d} 天\n";
echo "總天數: {$diff->days}\n";
echo "總小時: " . ($diff->days * 24 + $diff->h) . "\n";
// 方式二:時間戳相減(僅適用純天數、忽略 DST)
$days = (strtotime($end) - strtotime($start)) / 86400;
echo "簡易天數: " . floor($days) . "\n";
function isCurrentPeriod(string $date, string $period = 'week'): bool {
$dt = new DateTime($date);
$now = new DateTime();
return match ($period) {
'week' => $dt->format('o-W') === $now->format('o-W'), // ISO 週數年+週號
'month' => $dt->format('Y-m') === $now->format('Y-m'),
'year' => $dt->format('Y') === $now->format('Y'),
'quarter' => $dt->format('Y') === $now->format('Y')
&& ceil($dt->format('n') / 3) === ceil($now->format('n') / 3),
default => false,
};
}
// 測試
var_dump(isCurrentPeriod('2024-01-10', 'week'));
var_dump(isCurrentPeriod('2024-01-10', 'month'));
// 取得本月第一天/最後一天 00:00:00
function monthRange(int $year, int $month): array {
$first = new DateTime("{$year}-{$month}-01");
$last = clone $first;
$last->modify('last day of this month')->setTime(23, 59, 59);
return [$first, $last];
}
// 取得本週一/週日(ISO 8601 週一為週首)
function weekRange(DateTime $date = null): array {
$date = $date ?? new DateTime();
$mon = clone $date;
$mon->modify('this week Monday')->setTime(0, 0, 0);
$sun = clone $mon;
$sun->modify('+6 days')->setTime(23, 59, 59);
return [$mon, $sun];
}
// 格式化友好時間(如「3分鐘前」、「昨天 10:30」)
function timeAgo(string $datetime, string $lang = 'zh'): string {
$dt = new DateTime($datetime);
$now = new DateTime();
$diff = $now->getTimestamp() - $dt->getTimestamp();
if ($diff < 60) return $lang === 'zh' ? "{$diff}秒前" : "{$diff}s ago";
if ($diff < 3600) return $lang === 'zh' ? floor($diff/60) . "分鐘前" : floor($diff/60) . "m ago";
if ($diff < 86400) return $lang === 'zh' ? floor($diff/3600) . "小時前" : floor($diff/3600) . "h ago";
if ($diff < 604800) return $lang === 'zh' ? floor($diff/86400) . "天前" : floor($diff/86400) . "d ago";
return $dt->format($lang === 'zh' ? 'Y-m-d H:i' : 'M j, Y');
}
date_default_timezone_set('UTC') 放在入口文件最前// 正確的時區轉換
$utc = new DateTime('2024-01-15 10:00:00', new DateTimeZone('UTC'));
$utc->setTimezone(new DateTimeZone('Asia/Shanghai'));
echo $utc->format('Y-m-d H:i:s'); // 2024-01-15 18:00:00
strtotime() + date() 純函數式,避免物件創建開銷DateTimeImmutable 保證不可變性,減少 Bug日期時間看似基礎,實則是業務正確性的基石。建立團隊統一的時間處理規範文檔(存儲格式、時區策略、常用工具函數),能消除 90% 的時區相關 Bug。