PHP 日期時間瑞士刀:date() 格式化、strtotime() 解析、DateTime 物件導向完整指南
日期時間處理是 Web 開發高頻需求。PHP 提供程序式 date()/strtotime() 與物件導向 DateTime/DateTimeImmutable 兩套 API。本文系統整理格式化字元、相對時間解析、時區處理、常見陷阱、現代最佳實踐。
date() 格式化字元完整對照表
| 字元 | 說明 | 範例輸出 |
| Y | 4 位數年 | 2024 |
| y | 2 位數年 | 24 |
| m | 數字月份 (補零) | 01-12 |
| n | 數字月份 (不補零) | 1-12 |
| F | 完整月份名稱 | January |
| M | 縮寫月份 | Jan |
| d | 日期 (補零) | 01-31 |
| j | 日期 (不補零) | 1-31 |
| l | 完整星期名稱 | Monday |
| D | 縮寫星期 | Mon |
| N | ISO-8601 星期數 | 1(一)-7(日) |
| w | 星期數 (0=日) | 0-6 |
| H | 24小時制 (補零) | 00-23 |
| h | 12小時制 (補零) | 01-12 |
| i | 分鐘 (補零) | 00-59 |
| s | 秒 (補零) | 00-59 |
| a | am/pm 小寫 | am |
| A | AM/PM 大寫 | PM |
| U | Unix 時間戳 | 1704067200 |
| c | ISO 8601 格式 | 2024-01-01T12:00:00+08:00 |
| r | RFC 2822 格式 | Mon, 01 Jan 2024 12:00:00 +0800 |
常用格式組合
date('Y-m-d H:i:s'); // 2024-01-15 14:30:45 (資料庫標準)
date('Y/m/d'); // 2024/01/15
date('Y年m月d日 H:i'); // 2024年01月15日 14:30
date('c'); // 2024-01-15T14:30:45+08:00 (API 標準)
date(DATE_RFC3339); // 2024-01-15T14:30:45+08:00 (常數)
strtotime() 相對時間字串解析
| 字串 | 效果 | 範例 |
| +1 day | 明天 | strtotime('+1 day') |
| -1 day | 昨天 | strtotime('-1 day') |
| +1 week | 下週同時間 | strtotime('+1 week') |
| +1 month | 下月同日期 | strtotime('+1 month') |
| +1 year | 明年同日期 | strtotime('+1 year') |
| next Monday | 下個星期一 | strtotime('next Monday') |
| last Friday | 上個星期五 | strtotime('last Friday') |
| first day of this month | 本月 1 號 | strtotime('first day of this month') |
| last day of next month | 下月最後一天 | strtotime('last day of next month') |
| tomorrow | 明天 00:00 | strtotime('tomorrow') |
| yesterday | 昨天 00:00 | strtotime('yesterday') |
| now | 當前時間戳 | strtotime('now') = time() |
組合相對時間
strtotime('+1 week 2 days 3 hours 30 minutes');
// 等同
strtotime('+9 days 3 hours 30 minutes');
時區處理核心原則
// 1. 腳本層級設定 (最推薦,放在入口檔案)
date_default_timezone_set('Asia/Taipei'); // 或 Asia/Shanghai, UTC
// 2. php.ini 設定
// date.timezone = "Asia/Taipei"
// 3. DateTime 物件指定時區
$dt = new DateTime('now', new DateTimeZone('America/New_York'));
echo $dt->format('c'); // 含時區資訊
// 4. 時區轉換
$dt = new DateTime('2024-01-01 12:00', new DateTimeZone('Asia/Taipei'));
$dt->setTimezone(new DateTimeZone('UTC'));
echo $dt->format('c'); // 2024-01-01T04:00:00+00:00
DateTime 物件導向 API (PHP 5.2+ 推薦)
// 建立
$dt = new DateTime(); // 現在
$dt = new DateTime('2024-01-15 14:30'); // 指定時間
$dt = DateTime::createFromFormat('Y-m-d', '2024-01-15'); // 自定義格式解析
// 修改 (可鏈式)
$dt->modify('+1 day')->modify('noon'); // 明天中午
// 格式化
echo $dt->format('Y-m-d H:i:s');
// 比較
$dt1 = new DateTime('2024-01-01');
$dt2 = new DateTime('2024-12-31');
$diff = $dt1->diff($dt2);
echo $diff->days; // 365
echo $diff->format('%m月 %d天'); // 11月 30天
// 不可變版本 (PHP 5.5+,執行緒安全、函數式風格)
$dt = new DateTimeImmutable('2024-01-01');
$newDt = $dt->modify('+1 day'); // 回傳新物件,原物件不變
常見陷阱與解決方案
| 問題 | 原因 | 解決 |
| strtotime('+1 month') 在 1/31 給 3/3 | 月份天數不一 | 用 'first day of +1 month' 或 DateTime modify |
| 時間戳超出 32 位元 (2038 年) | 舊系統 time_t 限制 | PHP 64 位元無此問題、用 DateTime |
| 夏令時間切換導致時間錯誤 | DST 轉換 | 用具體時區 (Asia/Taipei 非 DST)、UTC 儲存 |
| date() 輸出非預期時區 | 未設 date_default_timezone_set | 入口檔案統一設定 |
| 微秒精度遺失 | date() 僅秒級 | DateTime::format('u')、microtime(true) |
實戰工具函數庫
// 友好相對時間 (如 "3分鐘前", "昨天 14:30")
function humanTime(int $timestamp): string {
$diff = time() - $timestamp;
if ($diff < 60) return $diff . '秒前';
if ($diff < 3600) return floor($diff/60) . '分鐘前';
if ($diff < 86400) return floor($diff/3600) . '小時前';
if ($diff < 604800) return floor($diff/86400) . '天前';
return date('Y-m-d H:i', $timestamp);
}
// 取得本週一 00:00
function weekStart(int $timestamp = null): int {
$dt = $timestamp ? new DateTime('@' . $timestamp) : new DateTime();
$dt->modify('this week Monday 00:00:00');
return $dt->getTimestamp();
}
// 取得本月第一天/最後一天
function monthRange(int $timestamp = null): array {
$dt = $timestamp ? new DateTime('@' . $timestamp) : new DateTime();
$start = clone $dt; $start->modify('first day of this month 00:00:00');
$end = clone $dt; $end->modify('last day of this month 23:59:59');
return [$start->getTimestamp(), $end->getTimestamp()];
}
Java 對比 (補充認知)
// Java 8+ java.time API
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(fmt);
ZonedDateTime taipei = now.atZone(ZoneId.of("Asia/Taipei"));
建立統一的時間處理工具類、入口檔案設定預設時區、優先使用 DateTimeImmutable,能避開 90% 日期時間 Bug,讓業務邏輯更專注核心功能。