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

PHP 日期間隔計算:年月周日精確差值與週數查詢完整指南

     編輯:PHP 綜合知識

前言

計算兩個日期之間的年、月、周、日差值是常見業務需求(會員天數、租賃計費、專案工期)。PHP `DateTime`/`DateInterval` 雖強大,但直接用 `diff()` 得到的 `y/m/d` 非直觀「總月數/總周數」。本文提供兩個實用函式:dateDiffDetail() 精確分解年月周日、getWeekInfo() 查詢指定日期所屬週數與起迄日。

一、核心函式:精確日期差值分解

函式簽名

/**
 * 計算兩日期差值,返回 ['years'=>, 'months'=>, 'weeks'=>, 'days'=>, 'total_days'=>]
 * @param string $start 起始日期 (Y-m-d)
 * @param string $end   結束日期 (Y-m-d)
 * @return array
 */
function dateDiffDetail(string $start, string $end): array

演算法邏輯

  1. 統一時間順序(確保 `$start <= $end`)。
  2. 計算總天數 `$totalDays`。
  3. 逐級遞減計算:
    • 年: 以 365/366 天估算,再用 `strtotime('+N year')` 微調確保不超過結束日。
    • 月: 在剩餘天數基礎上,以 30 天估算,再用 `strtotime('+N month')` 微調。
    • 周: 剩餘天數 `/ 7` 取整。
    • 日: 餘數。
  4. 過濾零值鍵,保持結果精簡。

完整程式碼

<?php
function dateDiffDetail(string $start, string $end): array {
    // 確保順序
    if (strtotime($start) > strtotime($end)) {
        [$start, $end] = [$end, $start];
    }

    $startTs = strtotime($start);
    $endTs   = strtotime($end);
    $totalDays = ($endTs - $startTs) / 86400;

    $result = ['total_days' => (int)$totalDays];

    if ($totalDays < 7) {
        $result['days'] = (int)$totalDays;
        return $result;
    }

    // 年
    $years = 0;
    while (strtotime("+{$years} year", $startTs) <= $endTs) $years++;
    $years = max(0, $years - 1);
    if ($years > 0) {
        $result['years'] = $years;
        $startTs = strtotime("+{$years} year", $startTs);
        $start = date('Y-m-d', $startTs);
    }

    // 月
    $months = 0;
    while (strtotime("+{$months} month", $startTs) <= $endTs) $months++;
    $months = max(0, $months - 1);
    if ($months > 0) {
        $result['months'] = $months;
        $startTs = strtotime("+{$months} month", $startTs);
    }

    // 剩餘天數
    $remainDays = ($endTs - $startTs) / 86400;
    if ($remainDays >= 7) {
        $result['weeks'] = (int)($remainDays / 7);
        $remainDays = $remainDays % 7;
    }
    if ($remainDays > 0) {
        $result['days'] = (int)$remainDays;
    }

    return array_filter($result);
}

// 測試
print_r(dateDiffDetail('2012-10-01', '2012-12-15'));
/*
Array
(
    [total_days] => 75
    [months] => 2
    [weeks] => 2
    [days] => 1
)
*/

二、週數查詢:指定日期所屬 ISO 週與起迄日

函式簽名

/**
 * 取得日期所屬 ISO 8601 週數、週一起始日、週日結束日
 * @param string $date 日期 (Y-m-d)
 * @return array ['week_num', 'week_start', 'week_end', 'week_start_cn', 'week_end_cn']
 */
function getWeekInfo(string $date): array

程式碼

function getWeekInfo(string $date): array {
    $ts = strtotime($date);
    // ISO 8601: 週一為週首,%V 為週數(01-53), %u 為星期(1=Mon..7=Sun)
    $weekNum = (int)strftime('%V', $ts);
    $weekDay = (int)strftime('%u', $ts); // 1-7

    $startTs = $ts - ($weekDay - 1) * 86400; // 週一
    $endTs   = $ts + (7 - $weekDay) * 86400; // 週日

    return [
        'week_num'       => $weekNum,
        'week_start'     => date('Y-m-d', $startTs),
        'week_end'       => date('Y-m-d', $endTs),
        'week_start_cn'  => strftime('%Y年%m月%d日', $startTs),
        'week_end_cn'    => strftime('%Y年%m月%d日', $endTs),
    ];
}

// 測試
print_r(getWeekInfo('2024-03-15'));
/*
Array
(
    [week_num] => 11
    [week_start] => 2024-03-11
    [week_end] => 2024-03-17
    [week_start_cn] => 2024年03月11日
    [week_end_cn] => 2024年03月17日
)
*/

三、現代 PHP 寫法:DateTime + DatePeriod(PHP 5.3+)

若需更物件導向、支援時區的實作:

use DateTime;
use DateInterval;

function dateDiffModern(string $start, string $end, string $tz = 'UTC'): array {
    $dt1 = new DateTime($start, new DateTimeZone($tz));
    $dt2 = new DateTime($end, new DateTimeZone($tz));
    if ($dt1 > $dt2) [$dt1, $dt2] = [$dt2, $dt1];

    $diff = $dt1->diff($dt2); // DateInterval

    // 總天數
    $totalDays = (int)$dt1->diff($dt2)->days;

    // 年月日直接取得
    $result = [
        'years'      => $diff->y,
        'months'     => $diff->m,
        'days'       => $diff->d,
        'total_days' => $totalDays,
    ];

    // 週數:總天數扣除年月後的餘天計算
    $remain = $totalDays - ($diff->y * 365 + $diff->m * 30); // 粗略
    if ($remain >= 7) {
        $result['weeks'] = (int)($remain / 7);
        $result['days']  = $remain % 7;
    }

    return array_filter($result);
}
注意: `DateInterval::days` 需 PHP 5.3+ 且由 `DateTime::diff()` 產生,手動 `new DateInterval` 不會填充此屬性。

四、常見陷阱與最佳實踐

陷阱解決方案
閏年/大小月導致月長不固定使用 `strtotime('+N month')` 由系統自動處理月末邊界
時區差異導致跨日錯誤統一使用 UTC 或指定時區,儲存/計算皆一致
開始/結束日是否包含明確業務定義:`diff` 不包含結束日,需+1 視情況調整
週數標準不一(ISO vs US)ISO 8601 週一起始(`%V/%u`),US 週日起始(`%U/%w`)
效能:大量日期計算預編譯時間戳、避免重複 `strtotime`、考慮向量化運算

五、總結

日期間隔計算看似簡單,實則涉及曆法邊界、時區、業務定義等細節。dateDiffDetail() 採用「由大到小遞減+系統曆法微調」策略,精確分解年月周日;getWeekInfo() 依循 ISO 8601 標準輸出週資訊。現代專案建議封裝為 `DateCalculator` 服務類,注入時區配置,統一對外提供 `diff()`、`weekOf()` 等介面,確保全系統計算邏輯一致。

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