中國大陸身分證字號編碼規則固定:15 碼(舊版)與 18 碼(新版)皆將生日編碼於固定位置,性別則由倒數第 2 位(15碼)或倒數第 2 位(18碼)奇偶決定。本文提供極簡解析函式,並說明原理。
<?php
/**
* 從身分證字號解析生日與性別
* @param string $idcard 身分證字號
* @return array ['birth' => 'YYYY-MM-DD', 'gender' => '男|女'] 或 false
*/
function parse_idcard(string $idcard): array|false
{
$idcard = strtoupper(trim($idcard));
$len = strlen($idcard);
// 格式快速檢核
if (!preg_match('/^(\d{15}|\d{17}[\dX])$/', $idcard)) {
return false;
}
// 解析生日
if ($len === 15) {
// 15碼:第7-12位 YYMMDD,年份補19
$birthRaw = '19' . substr($idcard, 6, 6);
} else {
// 18碼:第7-14位 YYYYMMDD
$birthRaw = substr($idcard, 6, 8);
}
$timestamp = strtotime($birthRaw);
if ($timestamp === false) {
return false; // 生日不合法
}
$birth = date('Y-m-d', $timestamp);
// 解析性別:倒數第2位(15碼為第14位,18碼為第17位),奇數=男、偶數=女
$sexDigit = $idcard[$len - 2]; // 統一取倒數第2個字元
$gender = ((int)$sexDigit % 2 === 1) ? '男' : '女';
return ['birth' => $birth, 'gender' => $gender];
}
// 測試
var_dump(parse_idcard('110105199003078531')); // 18碼
// ['birth' => '1990-03-07', 'gender' => '男']
var_dump(parse_idcard('110105900307853')); // 15碼
// ['birth' => '1990-03-07', 'gender' => '男']
| 版本 | 長度 | 生日位置 | 生日格式 | 性別位置 | 性別規則 |
|---|---|---|---|---|---|
| 舊版 | 15 | 7-12 (6 碼) | YYMMDD | 第 14 碼 (索引 13) | 奇=男、偶=女 |
| 新版 | 18 | 7-14 (8 碼) | YYYYMMDD | 第 17 碼 (索引 16) | 奇=男、偶=女 |
統一取 $idcard[strlen($idcard)-2] 即為性別碼,極度簡潔。
function validate_and_parse_idcard(string $idcard): array
{
$result = parse_idcard($idcard);
if ($result === false) {
throw new InvalidArgumentException('身分證字號格式錯誤或生日不合法');
}
// 18碼額外驗證校驗碼
if (strlen($idcard) === 18) {
$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)$idcard[$i] * $weights[$i];
}
if ($idcard[17] !== $checkMap[$sum % 11]) {
throw new InvalidArgumentException('身分證校驗碼錯誤');
}
}
return $result;
}
floor((time() - strtotime($birth)) / 31556926)substr 位元組偏移導致中文亂碼(身分證純數字+X 無此問題)。身分證字號編碼規則固定且公開,利用 PHP 字串操作與 strtotime 即可輕鬆解析生日與性別。將上述函式封裝為工具類,配合單元測試覆蓋邊界情況(閏年、15/18 碼轉換、X 校驗碼),即可安心應用於各類實名制業務中。