單純用年份相減(date('Y') - birthYear)無法處理「生日尚未到」的情況。精確年齡需考慮:年、月、日三層級比較,並正確處理閏年 2 月 29 日、大小月天數差異。
<?php
class AgeCalculator {
/**
* 計算精確年齡
* @param string $birthDate 出生日期 Y-m-d
* @param string|null $referenceDate 參考日期,預設今日
* @return array ['year'=>int, 'month'=>int, 'day'=>int, 'future'=>bool]
*/
public function calculate(string $birthDate, ?string $referenceDate = null): array {
$ref = $referenceDate ? new DateTime($referenceDate) : new DateTime('today');
$birth = new DateTime($birthDate);
// 未來出生日期檢查
$future = $birth > $ref;
if ($future) {
// 交換以便統一邏輯處理
[$birth, $ref] = [$ref, $birth];
}
// 先算完整年數
$years = 0;
$testBirth = clone $birth;
while ($testBirth->modify('+1 year') <= $ref) {
$years++;
}
// 算剩餘月數
$months = 0;
$testBirth = clone $birth;
$testBirth->modify("+{$years} years");
while ($testBirth->modify('+1 month') <= $ref) {
$months++;
}
// 算剩餘天數
$testBirth->modify("+{$months} months");
$days = $birth->diff($ref)->days; // 這裡較簡單用 diff 總天數再減去年月對應天數
// 更精確做法:
$days = 0;
while ($testBirth->modify('+1 day') <= $ref) {
$days++;
}
return [
'year' => $years,
'month' => $months,
'day' => $days,
'future' => $future
];
}
}
// 使用範例
$calc = new AgeCalculator();
$tests = [
'1990-06-12', '1990-07-13', '1990-08-16', '1990-10-10',
'1990-10-13', '1990-10-15', '1990-11-09', '1990-11-22',
'2016-11-22', '2016-08-22', '2016-10-13' // 未來日期測試
];
echo "參考日期: " . date('Y-m-d') . "\n";
foreach ($tests as $date) {
$age = $calc->calculate($date);
$prefix = $age['future'] ? '[未來日期] ' : '';
echo "{$prefix}{$date}: {$age['year']}歲 {$age['month']}月 {$age['day']}天\n";
}
?>
modify('+1 year') 自動處理 2/29 → 3/1)mktime() 夏令時間陷阱原文使用 while 迴圈逐年/月/日遞增,邏輯清晰但效能較差。現代 PHP DateTime::diff() 回傳 DateInterval 可直接取得年/月/日差異,但需注意 DateInterval 的 invert 屬性與總天數計算。
| 出生日期 | 參考日期 | 預期年齡 | 關鍵驗證點 |
|---|---|---|---|
| 2000-02-29 | 2024-02-28 | 23 歲 11 月 30 天 | 閏年生日非閏年怎算 |
| 2000-02-29 | 2024-03-01 | 24 歲 0 月 1 天 | 閏日後首日 |
| 2020-01-31 | 2020-02-29 | 0 歲 0 月 29 天 | 大月→小月跨月 |
| 2020-01-31 | 2020-03-01 | 0 歲 1 月 1 天 | 跨兩月 |
<?php
function getExactAge(string $birthDate, string $refDate = 'today'): array {
$birth = new DateTime($birthDate);
$ref = new DateTime($refDate);
if ($birth > $ref) return ['year'=>0,'month'=>0,'day'=>0,'future'=>true];
$diff = $birth->diff($ref);
return [
'year' => $diff->y,
'month' => $diff->m,
'day' => $diff->d,
'future' => false
];
}
?>
DateTime::diff() 內部已正確處理閏年與月份天數,PHP 5.3+ 可直接使用,程式碼簡潔且可靠。
date('Y') - date('Y', strtotime($birth)) 會在生日前多算 1 歲Y-m-d 格式,避免 strtotime() 解析歧義PHP 計算精確年齡的關鍵在於善用 DateTime 與 DateInterval 內建邏輯,避免手動處理曆法複雜度。現代專案請直接使用 $birth->diff($ref) 取得 y/m/d 屬性,既標準又可靠。若需支援 PHP 5.2 以下舊環境,才需採用手動遞增迴圈方案。