身分證字號第 7-14 碼為出生日期 (YYYYMMDD)。年齡計算規則:**生日當天算滿 1 歲**,未到生日不算。
<?php
function getAgeFromId(string $id): ?int {
// 1. 格式驗證 (台灣身分證: 1 字母 + 9 數字)
if (!preg_match('/^[A-Z][12]\d{8}$/', $id)) {
return null; // 或拋出 InvalidArgumentException
}
// 2. 解析出生日期
$birthStr = substr($id, 1, 8); // YYYYMMDD
$birth = DateTime::createFromFormat('Ymd', $birthStr);
if (!$birth) return null; // 非法日期 (如 20240230)
// 3. 計算年齡
$today = new DateTime('today'); // 00:00:00
$age = $today->diff($birth)->y;
// diff 已正確處理「生日是否已過」:若今天 < 今年生日,y 會少 1
return $age;
}
// 單元測試
$tests = [
['A123456789', '1990-01-01', 34], // 假設今天 2024-12-20
['A123456789', '2024-12-20', 0], // 今天生日
['A123456789', '2024-12-21', 0], // 生日未到
];
foreach ($tests as [$id, $birth, $expected]) {
$age = getAgeFromId($id);
echo "ID: $id, 年齡: $age, 預期: $expected, " . ($age === $expected ? '✅' : '❌') . "\n";
}
// 新制:第 2 碼 8=男, 9=女 (外國人統一證號另行規定)
if (!preg_match('/^[A-Z][1289]\d{8}$/', $id)) {
return null;
}
function validateTaiwanId(string $id): bool {
if (!preg_match('/^[A-Z][1289]\d{8}$/', $id)) return false;
// 字母對應數值表
$letterMap = [
'A'=>10,'B'=>11,'C'=>12,'D'=>13,'E'=>14,'F'=>15,'G'=>16,'H'=>17,'I'=>34,
'J'=>18,'K'=>19,'L'=>20,'M'=>21,'N'=>22,'O'=>35,'P'=>23,'Q'=>24,'R'=>25,
'S'=>26,'T'=>27,'U'=>28,'V'=>29,'W'=>32,'X'=>30,'Y'=>31,'Z'=>33
];
$c1 = $letterMap[$id[0]] ?? 0;
$n1 = (int)$id[1];
$sum = intdiv($c1, 10) + ($c1 % 10) * 9 + $n1 * 8;
for ($i = 2; $i <= 9; $i++) {
$sum += (int)$id[$i] * (10 - $i);
}
$check = (10 - $sum % 10) % 10;
return $check === (int)$id[10];
}
$letterMap 避免重複建立陣列。strtotime + 整數運算 (如原文),但 DateTime::diff 可讀性高、自動處理閏年、時區,現代 CPU 差異可忽略。