Download apps/core/utils/id_card_utils.py from jun2024/fc: direct link, hf CLI and curl.
- Browser
- Download file 8.78 kB
-
https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/core/utils/id_card_utils.py
- Command line
-
hf download hf://spaces/jun2024/fc/apps/core/utils/id_card_utils.py
-
curl -L -o id_card_utils.py https://huggingface.co/spaces/jun2024/fc/resolve/main/apps/core/utils/id_card_utils.py
8.78 kB
| """ | |
| 身份证号码解析工具类 | |
| 提供统一的身份证号码解析功能,支持 15 位和 18 位身份证. | |
| """ | |
| import logging | |
| from dataclasses import dataclass | |
| from datetime import date | |
| from django.utils.translation import gettext_lazy as _ | |
| logger = logging.getLogger(__name__) | |
| # 校验码权重因子 | |
| ID_CARD_WEIGHTS = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2] | |
| # 校验码对照表 | |
| ID_CARD_CHECK_CODES = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"] | |
| class IdCardInfo: | |
| """身份证信息数据类""" | |
| birth_date: str | None = None # 格式:YYYY年MM月DD日 | |
| gender: str | None = None # 男/女 | |
| age: int | None = None # 年龄 | |
| class IdCardUtils: | |
| """身份证号码解析工具类""" | |
| def parse_id_card_info(cls, id_number: str) -> IdCardInfo: | |
| """ | |
| 解析身份证号码,返回出生日期、性别和年龄 | |
| Args: | |
| id_number: 身份证号码(15位或18位) | |
| Returns: | |
| IdCardInfo: 包含出生日期、性别、年龄的数据类 | |
| """ | |
| if not id_number or len(id_number) < 14: | |
| return IdCardInfo() | |
| return IdCardInfo( | |
| birth_date=cls.extract_birth_date(id_number), | |
| gender=cls.extract_gender(id_number), | |
| age=cls.calculate_age(id_number), | |
| ) | |
| def extract_birth_date(cls, id_number: str) -> str | None: | |
| """ | |
| 从身份证号码提取出生日期 | |
| Args: | |
| id_number: 身份证号码 | |
| Returns: | |
| 格式化的出生日期字符串(YYYY年MM月DD日),解析失败返回 None | |
| """ | |
| if not id_number: | |
| return None | |
| try: | |
| if len(id_number) == 18: | |
| year = id_number[6:10] | |
| month = id_number[10:12] | |
| day = id_number[12:14] | |
| return f"{year}年{month}月{day}日" | |
| elif len(id_number) == 15: | |
| year = "19" + id_number[6:8] | |
| month = id_number[8:10] | |
| day = id_number[10:12] | |
| return f"{year}年{month}月{day}日" | |
| except (ValueError, IndexError) as e: | |
| logger.warning(f"解析身份证出生日期失败: {e}") | |
| return None | |
| def extract_gender(cls, id_number: str) -> str | None: | |
| """ | |
| 从身份证号码提取性别 | |
| Args: | |
| id_number: 身份证号码 | |
| Returns: | |
| 性别字符串(男/女),解析失败返回 None | |
| """ | |
| if not id_number or len(id_number) < 14: | |
| return None | |
| try: | |
| # 18位身份证:倒数第二位;15位身份证:最后一位 | |
| gender_digit = int(id_number[-2]) if len(id_number) == 18 else int(id_number[-1]) | |
| return "男" if gender_digit % 2 == 1 else "女" | |
| except (ValueError, IndexError) as e: | |
| logger.warning(f"解析身份证性别失败: {e}") | |
| return None | |
| def calculate_age(cls, id_number: str) -> int | None: | |
| """ | |
| 从身份证号码计算年龄 | |
| Args: | |
| id_number: 身份证号码 | |
| Returns: | |
| 年龄(整数),解析失败返回 None | |
| """ | |
| if not id_number: | |
| return None | |
| try: | |
| if len(id_number) == 18: | |
| birth_year = int(id_number[6:10]) | |
| birth_month = int(id_number[10:12]) | |
| birth_day = int(id_number[12:14]) | |
| elif len(id_number) == 15: | |
| birth_year = 1900 + int(id_number[6:8]) | |
| birth_month = int(id_number[8:10]) | |
| birth_day = int(id_number[10:12]) | |
| else: | |
| return None | |
| today = date.today() | |
| age = today.year - birth_year | |
| # 如果还没过生日,年龄减1 | |
| if (today.month, today.day) < (birth_month, birth_day): | |
| age -= 1 | |
| return age | |
| except (ValueError, IndexError) as e: | |
| logger.warning(f"计算年龄失败: {e}") | |
| return None | |
| def validate_id_card(cls, id_number: str) -> dict[str, str | bool]: | |
| """ | |
| 校验身份证号码是否合法 | |
| 校验规则: | |
| 1. 18位身份证: | |
| - 前17位必须为数字,第18位可以是数字或X(大小写不敏感) | |
| - 前6位为地区码(简单校验前两位是否为有效省份代码: 11-65) | |
| - 7-14位为出生日期,需要验证日期是否有效 | |
| - 第18位为校验码,根据前17位通过特定算法计算 | |
| 2. 15位身份证(旧版): | |
| - 全部为数字 | |
| - 前6位为地区码 | |
| - 7-12位为出生日期(YYMMDD),需要验证日期是否有效 | |
| Args: | |
| id_number: 身份证号码 | |
| Returns: | |
| dict: {"valid": bool, "message": str} | |
| """ | |
| if not id_number: | |
| return {"valid": False, "message": str(_("身份证号码不能为空"))} | |
| id_number = id_number.strip().upper() | |
| # 长度校验 | |
| if len(id_number) not in (15, 18): | |
| return {"valid": False, "message": str(_("身份证号码长度应为15位或18位"))} | |
| # 18位身份证校验 | |
| if len(id_number) == 18: | |
| return cls._validate_18_digit_id(id_number) | |
| # 15位身份证校验 | |
| return cls._validate_15_digit_id(id_number) | |
| def _validate_18_digit_id(cls, id_number: str) -> dict[str, str | bool]: | |
| """校验18位身份证号码""" | |
| # 前17位必须为数字 | |
| if not id_number[:17].isdigit(): | |
| return {"valid": False, "message": str(_("身份证前17位必须为数字"))} | |
| # 第18位校验 | |
| last_char = id_number[17] | |
| if not (last_char.isdigit() or last_char == "X"): | |
| return {"valid": False, "message": str(_("身份证第18位必须为数字或X"))} | |
| # 地区码校验(前两位: 11-65) | |
| province_code = int(id_number[:2]) | |
| if province_code < 11 or province_code > 65: | |
| return {"valid": False, "message": str(_("身份证地区码无效"))} | |
| # 出生日期校验 | |
| birth_date_str = id_number[6:14] | |
| if not cls._validate_birth_date(birth_date_str, is_18_digit=True): | |
| return {"valid": False, "message": str(_("身份证出生日期无效"))} | |
| # 校验码计算 | |
| check_sum = 0 | |
| for i in range(17): | |
| check_sum += int(id_number[i]) * ID_CARD_WEIGHTS[i] | |
| check_code = ID_CARD_CHECK_CODES[check_sum % 11] | |
| if id_number[17] != check_code: | |
| return {"valid": False, "message": str(_("身份证校验码错误,正确校验码应为 %(code)s")) % {"code": check_code}} | |
| return {"valid": True, "message": str(_("身份证号码格式正确"))} | |
| def _validate_15_digit_id(cls, id_number: str) -> dict[str, str | bool]: | |
| """校验15位身份证号码""" | |
| # 全部为数字 | |
| if not id_number.isdigit(): | |
| return {"valid": False, "message": str(_("15位身份证必须全部为数字"))} | |
| # 地区码校验(前两位: 11-65) | |
| province_code = int(id_number[:2]) | |
| if province_code < 11 or province_code > 65: | |
| return {"valid": False, "message": str(_("身份证地区码无效"))} | |
| # 出生日期校验(YYMMDD, 默认19XX年) | |
| birth_date_str = "19" + id_number[6:12] | |
| if not cls._validate_birth_date(birth_date_str, is_18_digit=True): | |
| return {"valid": False, "message": str(_("身份证出生日期无效"))} | |
| return {"valid": True, "message": str(_("身份证号码格式正确"))} | |
| def _validate_birth_date(cls, date_str: str, *, is_18_digit: bool) -> bool: | |
| """ | |
| 校验出生日期是否有效 | |
| Args: | |
| date_str: 日期字符串(YYYYMMDD格式) | |
| is_18_digit: 是否为18位身份证格式 | |
| Returns: | |
| bool: 日期是否有效 | |
| """ | |
| if len(date_str) != 8: | |
| return False | |
| try: | |
| year = int(date_str[:4]) | |
| month = int(date_str[4:6]) | |
| day = int(date_str[6:8]) | |
| # 基本范围校验 | |
| if month < 1 or month > 12: | |
| return False | |
| if day < 1 or day > 31: | |
| return False | |
| # 年份范围校验 | |
| current_year = date.today().year | |
| if year < 1900 or year > current_year: | |
| return False | |
| # 具体日期校验 | |
| date(year, month, day) | |
| return True | |
| except (ValueError, TypeError): | |
| return False | |