/**
* 根据身份证号码计算年龄(兼容15/18位,含合法性校验)
* @param string $idCard 身份证号码
* @return int|false 成功返回年龄,失败返回false
*/
function getAgeFromIdCard(string $idCard)
{
// 第一步:校验身份证格式(基础正则)
$idCard = trim($idCard);
$pattern18 = '/^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))((0[1-9])|([1-2]\d)|(3[0-1]))\d{3}[\dXx]$/';
$pattern15 = '/^[1-9]\d{5}\d{2}((0[1-9])|(1[0-2]))((0[1-9])|([1-2]\d)|(3[0-1]))\d{3}$/';
if (!preg_match($pattern18, $idCard) && !preg_match($pattern15, $idCard)) {
return false; // 身份证格式非法
}
// 第二步:解析出生日期
$birthDate = '';
if (strlen($idCard) === 18) {
// 18位:截取7-14位(YYYYMMDD)
$birthDate = substr($idCard, 6, 8);
} elseif (strlen($idCard) === 15) {
// 15位:截取7-12位(YYMMDD),补全为19YYMMDD(15位身份证均为19xx年出生)
$birthDate = '19' . substr($idCard, 6, 6);
}
// 第三步:格式化出生日期为 DateTime 对象(严格格式校验 Ymd)
try {
$birth = DateTime::createFromFormat('Ymd', $birthDate);
if (!$birth) {
return false; // 出生日期不合法(如19990230、19941301)
}
// 可选:固定基准时间(用于测试,如2026-01-17),生产环境可注释,使用服务器当前时间
$now = DateTime::createFromFormat('Y-m-d H:i:s', '2026-01-17 10:11:30');
// 生产环境:使用服务器当前时间
// $now = new DateTime();
// 第四步:计算年龄(核心修正:直接使用 diff()->y,无需重复减1)
$age = $now->diff($birth)->y;
// 优化:仅处理边界场景(避免负数年龄,如未来出生日期)
$finalAge = $age < 0 ? 0 : $age;
// 第五步:完善日志,便于排查
// think\facade\Log::warning('根据身份证号码计算年龄成功:身份证=' . $idCard . ',出生日期=' . $birth->format('Y-m-d') . ',
// 当前时间=' . $now->format('Y-m-d H:i:s') . ',最终年龄=' . $finalAge);
return $finalAge;
} catch (Exception $e) {
think\facade\Log::error('身份证年龄计算异常:' . $idCard . ',异常信息:' . $e->getMessage());
return false;
}
}