身份證號碼編碼規則國家標準(GB 11643-1999)規定:第 7-14 位為出生日期(YYYYMMDD),15 位舊版身份證年份僅保留後兩位(19XX)。本文提供健壯的解析函數,含格式校驗、日期合法性檢查、成年判定。
| 版本 | 長度 | 出生日期位置 | 年份格式 |
|---|---|---|---|
| 舊版 | 15 位 | 第 7-12 位 | YYMMDD (19XX) |
| 新版 | 18 位 | 第 7-14 位 | YYYYMMDD |
/**
* 解析身份證號,返回生日、是否成年、錯誤碼
* @param string $idCard 身份證號碼
* @param int $format 1=YYYY-MM-DD, 0=MM-DD
* @return array ['error'=>0/1/2, 'isAdult'=>0/1, 'birthday'=>'', 'gender'=>'male/female']
*/
function parseIdCard(string $idCard, int $format = 1): array {
$result = [
'error' => 0, // 0=未知錯誤, 1=格式錯誤, 2=成功
'isAdult' => 0, // 0=成年, 1=未成年
'birthday' => '',
'gender' => '',
];
// 1. 格式正則校驗(支援大小寫 X)
if (!preg_match('/^(\d{15}|\d{17}[\dXx])$/', $idCard)) {
$result['error'] = 1;
return $result;
}
// 2. 提取出生日期
if (strlen($idCard) === 18) {
$year = (int)substr($idCard, 6, 4);
$month = (int)substr($idCard, 10, 2);
$day = (int)substr($idCard, 12, 2);
// 性別:倒數第 2 位奇男偶女
$result['gender'] = ((int)substr($idCard, 16, 1) % 2) ? 'male' : 'female';
} else { // 15 位
$year = 1900 + (int)substr($idCard, 6, 2);
$month = (int)substr($idCard, 8, 2);
$day = (int)substr($idCard, 10, 2);
$result['gender'] = ((int)substr($idCard, 14, 1) % 2) ? 'male' : 'female';
}
// 3. 日期合法性檢查
if (!checkdate($month, $day, $year)) {
$result['error'] = 1;
return $result;
}
// 合理性範圍:100 歲內、非未來
$nowYear = (int)date('Y');
if ($year < $nowYear - 100 || $year > $nowYear) {
$result['error'] = 1;
return $result;
}
// 4. 格式化輸出
$result['birthday'] = $format
? sprintf('%04d-%02d-%02d', $year, $month, $day)
: sprintf('%02d-%02d', $month, $day);
// 5. 成年判定(18 歲整)
$birthTimestamp = mktime(0, 0, 0, $month, $day, $year);
$ageSeconds = time() - $birthTimestamp;
$result['isAdult'] = ($ageSeconds >= 18 * 365 * 24 * 3600) ? 0 : 1;
$result['error'] = 2;
return $result;
}
$tests = [
'110101199003071234', // 18 位
'110101900307123', // 15 位
'11010120101231123X', // 含 X
'invalid', // 錯誤格式
];
foreach ($tests as $id) {
$info = parseIdCard($id);
echo "ID: {$id}\n";
echo " 錯誤碼: {$info['error']} (2=成功)\n";
echo " 生日: {$info['birthday']}\n";
echo " 性別: {$info['gender']}\n";
echo " 成年: " . ($info['isAdult'] ? '否' : '是') . "\n\n";
}
GB/T 2260 標準查詢戶籍地DateTime::diff()110101******1234)function validateChecksum(string $id18): bool {
if (strlen($id18) !== 18) return false;
$weights = [7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2];
$checkMap = ['1','0','X','9','8','7','6','5','4','3','2'];
$sum = 0;
for ($i = 0; $i < 17; $i++) {
$sum += (int)$id18[$i] * $weights[$i];
}
return strtoupper($id18[17]) === $checkMap[$sum % 11];
}
身份證解析看似簡單,實則涉及正則校驗、日期邊界、編碼規則演進等多維考量。生產環境建議封裝為獨立 Composer 包,配合單元測試覆蓋各種邊界案例,確保業務穩健。