首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 日期時間差計算:DateTime 類別、時間戳與資料庫方案完整比較

     編輯:PHP 相關知識

計算日期時間差是常見需求,PHP 提供多種方式:`DateTime` 物件導向介面、`date_diff()` 函式、時間戳運算、資料庫函式。本文比較各方法優缺點並提供最佳實踐範例。

現代標準做法:DateTime + DateInterval

PHP 5.3+ 強烈建議使用 `DateTime` 類別,支援時區、夏令時間、閏年等複雜邏輯。

基本差值計算

<?php
$start = new DateTime('2023-01-15 09:30:00');
$end = new DateTime('2023-12-25 18:45:00');

$interval = $start->diff($end);

echo "相差 {$interval->y} 年 {$interval->m} 月 {$interval->d} 天 ";
echo "{$interval->h} 時 {$interval->i} 分 {$interval->s} 秒\n";

// 總天數
echo "總天數:{$interval->days}\n";
// 總秒數
$totalSeconds = $interval->days * 86400 + $interval->h * 3600 + $interval->i * 60 + $interval->s;
echo "總秒數:{$totalSeconds}\n";
?>

人性化相對時間(如「3 天前」)

<?php
function humanTimeDiff(DateTime $from, DateTime $to = null): string {
    $to = $to ?? new DateTime();
    $interval = $from->diff($to);
    
    $units = [
        'y' => ['年', '年'],
        'm' => ['個月', '個月'],
        'd' => ['天', '天'],
        'h' => ['小時', '小時'],
        'i' => ['分鐘', '分鐘'],
        's' => ['秒', '秒']
    ];
    
    foreach ($units as $prop => [$singular, $plural]) {
        $value = $interval->$prop;
        if ($value > 0) {
            return "{$value}{$singular}前";
        }
    }
    return '剛剛';
}

// 使用
$postTime = new DateTime('2023-12-20 14:30:00');
echo humanTimeDiff($postTime); // 輸出:5天前(假設現在是 12/25)
?>

計算工作天數(排除週末)

<?php
function businessDaysBetween(DateTime $start, DateTime $end): int {
    $start = clone $start;
    $end = clone $end;
    $start->setTime(0, 0, 0);
    $end->setTime(0, 0, 0);
    
    if ($start > $end) {
        [$start, $end] = [$end, $start];
    }
    
    $days = 0;
    while ($start <= $end) {
        $dayOfWeek = (int)$start->format('N'); // 1=週一 ... 7=週日
        if ($dayOfWeek < 6) { // 週一至週五
            $days++;
        }
        $start->modify('+1 day');
    }
    return $days;
}

// 進階:排除國定假日
function businessDaysExcludingHolidays(DateTime $start, DateTime $end, array $holidays = []): int {
    $holidaySet = array_flip(array_map(fn($d) => $d->format('Y-m-d'), $holidays));
    $count = 0;
    $current = clone $start;
    $current->setTime(0, 0, 0);
    $endClone = clone $end;
    $endClone->setTime(0, 0, 0);
    
    while ($current <= $endClone) {
        $dow = (int)$current->format('N');
        $dateStr = $current->format('Y-m-d');
        if ($dow < 6 && !isset($holidaySet[$dateStr])) {
            $count++;
        }
        $current->modify('+1 day');
    }
    return $count;
}
?>

時間戳運算(舊式但仍有用)

<?php
$start = strtotime('2023-01-15');
$end = strtotime('2023-12-25');

$diffSeconds = $end - $start;
$diffDays = floor($diffSeconds / 86400);

echo "相差 {$diffDays} 天\n";

// 注意:strtotime 受限於 32 位元系統的 2038 年問題
// 64 位元 PHP 無此限制
?>

資料庫層面計算

MySQL

-- 日期差(天數)
SELECT DATEDIFF('2023-12-25', '2023-01-15') AS days_diff; -- 344

-- 時間差(秒數)
SELECT TIMESTAMPDIFF(SECOND, '2023-01-15 09:30:00', '2023-12-25 18:45:00') AS seconds_diff;

-- 多種單位
SELECT 
    TIMESTAMPDIFF(YEAR, start_dt, end_dt) AS years,
    TIMESTAMPDIFF(MONTH, start_dt, end_dt) % 12 AS months,
    TIMESTAMPDIFF(DAY, start_dt, end_dt) % 30 AS days;

PostgreSQL

-- 間隔類型
SELECT age('2023-12-25 18:45:00'::timestamptz, '2023-01-15 09:30:00'::timestamptz) AS age_interval;
-- 輸出:1 year 11 mons 10 days 09:15:00

-- 總天數
SELECT ('2023-12-25'::date - '2023-01-15'::date) AS days_diff; -- 344

常見陷阱與解決方案

問題原因解決
月份長度不固定導致月差不準`diff()` 回傳曆月差,非固定 30 天用天數換算或接受曆月定義
時區導致日期跳躍未指定時區或時區不一致統一用 UTC 儲存,顯示時轉換
夏令時間導致 23/25 小時天DST 切換日用 `diff()` 自動處理,避免手算秒數
閏年 2/29 計算非閏年無 2/29`DateTime` 內建處理
2038 年問題32 位元 `time_t` 溢位用 64 位元 PHP + `DateTime`

時區最佳實踐

<?php
// 1. 程式啟動時設定預設時區
date_default_timezone_set('Asia/Taipei');

// 2. 資料庫儲存用 UTC
$utc = new DateTime('now', new DateTimeZone('UTC'));
$stmt = $pdo->prepare('INSERT INTO events (event_time_utc) VALUES (?)');
$stmt->execute([$utc->format('Y-m-d H:i:s')]);

// 3. 讀取時轉換為使用者時區
function toUserTimezone(string $utcDateTime, string $userTz = 'Asia/Taipei'): string {
    $dt = new DateTime($utcDateTime, new DateTimeZone('UTC'));
    $dt->setTimezone(new DateTimeZone($userTz));
    return $dt->format('Y-m-d H:i:s');
}

// 4. 前端傳送 ISO 8601 含時區字串
// "2023-12-25T18:45:00+08:00"
$dt = new DateTime($_POST['event_time']); // 自動解析時區
$utc = clone $dt;
$utc->setTimezone(new DateTimeZone('UTC'));
?>

效能比較(百萬次運算)

<?php
// 基準測試
$iterations = 1_000_000;
$start = '2023-01-01';
$end = '2023-12-31';

// DateTime::diff
$t = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    (new DateTime($start))->diff(new DateTime($end));
}
echo "DateTime::diff: " . (microtime(true) - $t) . "s\n";

// strtotime 減法
$t = microtime(true);
for ($i = 0; $i < $iterations; $i++) {
    (strtotime($end) - strtotime($start)) / 86400;
}
echo "strtotime: " . (microtime(true) - $t) . "s\n";

// 通常 strtotime 較快但功能有限,DateTime 功能完整且安全
?>

**總結**:現代 PHP 專案應優先使用 `DateTime`/`DateTimeImmutable` 類別配合 `diff()` 方法,功能最完整、時區處理正確、避免 2038 年問題。資料庫層面計算適合報表查詢、大量資料過濾。時間戳運算僅適合簡單天數差且不涉及時區的舊代碼維護。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved