You are here: 首頁 PHP分享特區 DateTime::diff() 計算 年差異 出現錯誤問題

飛朵啦學習手札

本網站建議使用Firefox2.0以上,或是使用Goole瀏覽器來瀏覽,並使用1024x768解析度來觀看.

DateTime::diff() 計算 年差異 出現錯誤問題

E-mail 列印 PDF

這一個問題是在計算年資時,出現了差異,DEBUG 數據如下:

===== DEBUG START =====
person_id = 1273
raw due_date = 2024-03-01
TODAY = 2026-03-12
NYD = 2027-01-01
calculateAnnualLeaveType2(date, TODAY) = 
Array
(
	[completed_years]=>1
    [leave_days] => 7
    [source_date] => 2026-03-01
    [expired_at] => 2027-02-28
    [title] => 115.03.01-116.02.28
)
===== DEBUG END =====

這是特休,理論上年度應該是兩年,計算是這樣:

 $hire = new DateTime($hire_date);  $last_anniversary = DateTime::createFromFormat('Y-m-d', $year . '-' . $md);  $completed_years = $hire->diff($last_anniversary)->y;

因為找不出原因,乾脆直接改成手算年資。

 $hire_y = (int)$hire->format('Y');
 $today_y = (int)$today->format('Y');
 $hire_md = $hire->format('m-d');
 $today_md = $today->format('m-d');

 // 已滿幾年:不用 diff()->y,改手算,避免環境差異
 $completed_years = $today_y - $hire_y;
 if ($today_md < $hire_md) {
$completed_years--;
 }
 if ($completed_years < 0) {
$completed_years = 0;
 }

結果就正常了:

Array
(
   [completed_years]=>2
    [leave_days] => 10
    [source_date] => 2026-03-01
    [expired_at] => 2027-02-28
    [title] => 115.03.01-116.02.28
)

詢問了chatgpt 給出的答案是:


$hire->diff($last_anniversary)->y 這個內建本身通常是可靠的,PHP 官方把 DateTimeInterface::diff() 定義成回傳兩個日期時間之間的差異,而且它是依照完整日期時間 來算,不是只看年月日;另外它還會考慮 DST(夏令時間)變化。(php.net)

真正容易出問題的,通常不是 diff() 壞掉,而是輸入給它的 DateTime 物件不夠乾淨。 PHP 官方文件明講:createFromFormat() 若沒有用 !|未提供的欄位會先用「目前的日期時間」初始化;所以你只是想解析 Y-m-d,卻沒有先歸零, 就可能把當下的時分秒帶進去,導致「看起來同一天,實際差不到完整 1 年 / 2 年」。(php.net)

所以實務上我會這樣判斷:

  • 拿來算一般日期差:很穩。(php.net)

  • 拿來算年資、特休、到職週年這種 HR 規則:可以用,但前提是你要先把輸入標準化成純日期 ,否則很容易被時分秒、時區、DST 影響。(php.net)

  • 若規則本質是「比年月日」而不是「比完整 timestamp」,像你的特休這種,我反而會更偏向你現在改的 「手算 completed_years」做法,因為它更貼近制度規則,也更容易除錯。

換句話說,這不是說 diff()->y 很爛,而是:

diff()->y 比較像「時間物件差幾個完整年」
但你的需求其實是 「年資制度上已滿幾年」

這兩者很多時候相同,但在邊界上不一定完全一樣。

你現在這樣改成手算年資,我會認為是比較保險的做法。尤其在特休、年資、合約年限、 生日到齡這類規則上,手算年月日邊界往往比 diff()->y 更符合商業邏輯

最近更新 ( 週五, 13 三月 2026 08:46 )  
 

新增回應


驗證碼
更新