看似簡單的「兩個日期相差天數」,實際涉及時區偏移、夏令時切換、閏年閏秒等邊界條件。本文從時間戳減法進階到 DateTime/DateInterval 標準庫方案,給出生產環境可靠的計算代碼。
<?php
/**
* 簡易版:基於 UTC 時間戳相減
* ⚠️ 缺陷:忽略時區、夏令時導致邊界日誤差 ±1 天
*/
function diffDaysSimple(string $day1, string $day2): float
{
$ts1 = strtotime($day1);
$ts2 = strtotime($day2);
if ($ts1 === false || $ts2 === false) {
throw new InvalidArgumentException('無效日期格式');
}
// 絕對值天數,不考慮方向
return abs($ts1 - $ts2) / 86400;
}
// 測試
echo diffDaysSimple('2013-07-27', '2013-08-04'); // 8
<?php
/**
* 標準版:支援時區、夏令時、閏年,返回整數天數差
* @param string $date1 格式如 '2023-03-15' 或 '2023-03-15 14:30:00'
* @param string $date2
* @param string $timezone 時區標識,如 'Asia/Shanghai'、'UTC'
* @return int 日期差($date1 - $date2),可為負
*/
function diffDaysStandard(string $date1, string $date2, string $timezone = 'UTC'): int
{
$tz = new DateTimeZone($timezone);
$dt1 = new DateTime($date1, $tz);
$dt2 = new DateTime($date2, $tz);
// 將時間歸零到 00:00:00,純比較「日曆日期」
$dt1->setTime(0, 0, 0);
$dt2->setTime(0, 0, 0);
$interval = $dt1->diff($dt2);
// invert=1 表示 $dt1 < $dt2,結果為負
return $interval->invert ? -$interval->days : $interval->days;
}
// 夏令時邊界測試(美東時間 2024-03-10 2:00 跳轉 3:00)
echo diffDaysStandard('2024-03-09', '2024-03-11', 'America/New_York'); // 2 天(而非 1.958...)
echo diffDaysStandard('2023-02-28', '2024-02-28', 'UTC'); // 366 天(閏年)
<?php
// composer require nesbot/carbon
use Carbon\Carbon;
$days = Carbon::parse('2023-01-01')->diffInDays('2023-12-31'); // 364
$daysAbs = Carbon::parse('2023-01-01')->diffInDays('2023-12-31', true); // 絕對值 364
// 極其豐富的語義化方法
$dt = Carbon::parse('2023-05-20');
echo $dt->diffForHumans(); // "5 months ago"
echo $dt->addDays(10)->isWeekend(); // false
| 維度 | strtotime 減法 | DateTime::diff | Carbon |
|---|---|---|---|
| 時區感知 | ❌ 依賴 php.ini date.timezone | ✅ 顯式傳入 DateTimeZone | ✅ 全鏈路不可變時區 |
| 夏令時正確 | ❌ 23/25 小時日誤差 | ✅ 內部用 UTC 計算再轉換 | ✅ 同 DateTime |
| 閏秒處理 | ❌ 忽略 | ❌ 忽略(同 Unix 時間戳) | ❌ 忽略 |
| 返回類型 | float(含小數天) | int(整數天) | int/float 可選 |
| 依賴 | 零依賴 | 零依賴(PHP 5.3+) | Composer 包 |
diffDaysSimple 足夠,性能最高。diffDaysStandard 指定用戶時區,避免「跨日 0 點」邏輯錯誤。leap-seconds.list 或使用專業時間庫(如 PHP-ICU),標準庫不支援閏秒。錯誤:
floor((strtotime($d1) - strtotime($d2)) / 86400)
原因:夏令時切換日實際秒數 ≠ 86400,導致跨越切換日時差 ±1。
修正:統一用DateTime::setTime(0,0,0)歸零後再diff()。
日期計算無小事,選對工具、鎖定時區、測試邊界,才能讓「相差天數」在所有業務場景下精準可信。