/**
* 依身分證字號計算滿年齡
* @param string $id 身分證字號(含舊制 10 碼、新制 11 碼)
* @return int|false 年齡,格式錯誤回傳 false
*/
function getAgeFromId(string $id): int|false {
// 1. 格式驗證(簡化:長度 10 或 11、前 2 碼格式)
$id = trim($id);
if (!preg_match('/^[A-Z][1289]\d{8,9}$/i', $id)) return false;
// 2. 解析出生日期(第 3-8 碼,共 6 碼:YYMMDD)
$birthStr = substr($id, 2, 6); // 例:900101
if (strlen($birthStr) !== 6) return false;
$rocYear = (int) substr($birthStr, 0, 2); // 民國年
$month = (int) substr($birthStr, 2, 2);
$day = (int) substr($birthStr, 4, 2);
// 3. 驗證月份日期合法性
if ($month < 1 || $month > 12 || $day < 1 || $day > 31) return false;
if (!checkdate($month, $day, $rocYear + 1911)) return false; // 民國轉西元
// 4. 建立生日 DateTime(時區設為 Asia/Taipei)
$birthDate = new DateTime(
sprintf('%04d-%02d-%02d', $rocYear + 1911, $month, $day),
new DateTimeZone('Asia/Taipei')
);
// 5. 今日日期(同時區)
$today = new DateTime('today', new DateTimeZone('Asia/Taipei'));
// 6. 計算滿年齡:生日已過則 +1,未過則不加
$age = $today->diff($birthDate)->y;
// diff 已正確處理「生日未到」情況,直接回傳 y 即為滿年齡
return $age;
}
// 使用範例
echo getAgeFromId('A123456789'); // 依出生日期計算
echo getAgeFromId('A1900101123'); // 新制 11 碼
| 細節 | 處理方式 |
|---|---|
| 民國年轉西元 | + 1911(民國 1 年 = 1912 年) |
| 生日未到邏輯 | DateTime::diff() 內建正確計算滿年齡,無需手動比較月日 |
| 時區 | 強制 Asia/Taipei,避免跨日界問題 |
| 舊制 10 碼 / 新制 11 碼 | 同樣取第 3-8 碼為出生日期,長度差異在流水號與檢查碼 |
| 外籍/大陸港澳 (性別碼 8/9) | 同樣適用,出生日期格式相同 |
| 檢查碼驗證 | 本函式未實作,如需完整驗證請加入加權加總模 10 算法 |
function getAgesBatch(array $ids): array {
$today = new DateTime('today', new DateTimeZone('Asia/Taipei'));
$results = [];
foreach ($ids as $id) {
$results[$id] = getAgeFromId($id); // 可內聯優化,避免重複建立 $today
}
return $results;
}
善用 DateTime::diff() 內建滿年齡計算、強制時區、驗證月份日期合法性,即可穩健實現「身分證字號計算年齡」。批次處理時複用 DateTime('today') 物件,避免重複建立開銷。如需完整身分證驗證(檢查碼、地區碼對照),建議引入成熟套件(如 laravel-id-validation、taiwan-id-validator)。