PHP 有兩套並行的日期時間 API:
建議:全面遷移至 DateTime 系列,棄用 `date()`/`strtotime()` 等舊函數。
| 任務 | 代碼範例 |
|---|---|
| 建立當前時間(預設時區) | `$dt = new DateTime();` 或 `new DateTimeImmutable();` |
| 指定時區建立 | `$dt = new DateTime('now', new DateTimeZone('Asia/Taipei'));` |
| 解析字串(支援相對時間) | `$dt = new DateTime('2024-01-15 14:30:00');` `$dt = new DateTime('+1 week');` `$dt = new DateTime('first day of next month');` |
| 格式化輸出 | `$dt->format('Y-m-d H:i:s');` `$dt->format('U'); // 時間戳` |
| 修改時區(不改變瞬間) | `$dt->setTimezone(new DateTimeZone('UTC'));` |
| 加減時間(可變) | `$dt->add(new DateInterval('P1D'));` `$dt->sub(new DateInterval('PT30M'));` |
| 加減時間(不可變,推薦) | `$newDt = $dt->add(new DateInterval('P1D'));` // 返回新實例 |
| 計算差值 | `$diff = $dt1->diff($dt2);` // 返回 DateInterval `$diff->days, $diff->h, $diff->i, $diff->format('%a 天 %h 小時')` |
| 比較大小 | `$dt1 > $dt2`、`$dt1 == $dt2`、`$dt1 < $dt2` 直接支援 |
P[n]Y[n]M[n]DT[n]H[n]M[n]S
// 範例
P1Y // 1 年
P3M // 3 個月
P2W // 2 週
P10D // 10 天
PT5H // 5 小時
PT30M // 30 分鐘
P1DT6H // 1 天 6 小時
$start = new DateTime('2024-01-01');
$interval = new DateInterval('P1D'); // 每天
$end = (new DateTime('2024-01-10'))->modify('+1 day'); // 包含結束日需 +1
$period = new DatePeriod($start, $interval, $end);
foreach ($period as $dt) {
echo $dt->format('Y-m-d') . "\n";
}
// 輸出 2024-01-01 ~ 2024-01-10
function getAge(DateTime $birth): int {
$today = new DateTime('today');
$diff = $birth->diff($today);
return $diff->y;
}
$monday = (new DateTime('monday this week'))->setTime(0,0,0);
$sunday = (new DateTime('sunday this week'))->setTime(23,59,59);
function isToday(DateTime $dt): bool {
return $dt->format('Y-m-d') === (new DateTime())->format('Y-m-d');
}
function humanDiff(DateTime $dt): string {
$now = new DateTime();
$diff = $dt->diff($now);
if ($diff->y) return $diff->format('%y 年前');
if ($diff->m) return $diff->format('%m 個月前');
if ($diff->d) {
if ($diff->d == 1) return '昨天 ' . $dt->format('H:i');
if ($diff->d < 7) return $diff->format('%d 天前');
return $dt->format('Y-m-d');
}
if ($diff->h) return $diff->format('%h 小時前');
if ($diff->i) return $diff->format('%i 分鐘前');
return '剛剛';
}
function parseFlexible(string $input): ?DateTime {
$formats = [
'Y-m-d H:i:s', 'Y-m-d', 'Y/m/d H:i:s', 'Y/m/d',
'd-m-Y H:i:s', 'd.m.Y', 'U', // 時間戳
];
foreach ($formats as $fmt) {
$dt = DateTime::createFromFormat($fmt, $input, new DateTimeZone('UTC'));
if ($dt !== false) return $dt;
}
// 最後嘗試 strtotime 相對格式
$ts = strtotime($input);
return $ts !== false ? (new DateTime('@'.$ts))->setTimezone(new DateTimeZone('UTC')) : null;
}
| 陷阱 | 正確做法 |
|---|---|
| 在循環中重複 `new DateTimeZone()` | 建立一次 `$tz = new DateTimeZone('Asia/Taipei');` 重複使用 |
| 用 `date('Y-m-d', strtotime('+1 month'))` 處理月底 | 用 `$dt->modify('first day of +1 month')` 或 `DateInterval` |
| 直接比較格式化字串 `$dt1->format('Ymd') > $dt2->format('Ymd')` | 直接比較物件 `$dt1 > $dt2` |
| 忘記 `DateTimeImmutable` 導致副作用 | 函數參數/返回值優先用 `DateTimeImmutable`,除非需就地修改 |
PHP 的 `DateTime` 系列功能強大、符合國際標準、時區安全。掌握 物件導向 API、ISO 8601 週期、DatePeriod 序列、UTC 儲存策略,即可從容應對 99% 的日期時間業務。拋棄舊式 `date()`/`strtotime()`,擁抱現代、可測試、可維護的時間處理方式。