QinGang_interested/lib/tools/id_cart_util.dart

254 lines
7.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// utils/id_card_util.dart
import 'dart:core';
class IDCardInfo {
final String raw;
final bool isValid;
final String? error;
final String? id18; // 标准化到18位若输入15位则自动转换
final String? provinceCode;
final String? province;
final DateTime? birthDate;
final String? birth; // YYYY-MM-DD
final int? age;
final String? gender; // '男' / '女'
final bool checksumValid;
final String? constellation; // 星座
final String? zodiac; // 生肖
IDCardInfo({
required this.raw,
required this.isValid,
this.error,
this.id18,
this.provinceCode,
this.province,
this.birthDate,
this.birth,
this.age,
this.gender,
required this.checksumValid,
this.constellation,
this.zodiac,
});
Map<String, dynamic> toJson() => {
'raw': raw,
'isValid': isValid,
'error': error,
'id18': id18,
'provinceCode': provinceCode,
'province': province,
'birth': birth,
'age': age,
'gender': gender,
'checksumValid': checksumValid,
'constellation': constellation,
'zodiac': zodiac,
};
@override
String toString() => toJson().toString();
}
/// 主要调用函数:传入身份证号字符串,返回 IDCardInfo
IDCardInfo parseChineseIDCard(String id) {
final raw = (id ?? '').toString().trim().toUpperCase();
if (raw.isEmpty) {
return IDCardInfo(raw: raw, isValid: false, error: '空字符串', checksumValid: false);
}
// 省份码映射(常见)
const provinceMap = <String, String>{
'11': '北京市',
'12': '天津市',
'13': '河北省',
'14': '山西省',
'15': '内蒙古自治区',
'21': '辽宁省',
'22': '吉林省',
'23': '黑龙江省',
'31': '上海市',
'32': '江苏省',
'33': '浙江省',
'34': '安徽省',
'35': '福建省',
'36': '江西省',
'37': '山东省',
'41': '河南省',
'42': '湖北省',
'43': '湖南省',
'44': '广东省',
'45': '广西壮族自治区',
'46': '海南省',
'50': '重庆市',
'51': '四川省',
'52': '贵州省',
'53': '云南省',
'54': '西藏自治区',
'61': '陕西省',
'62': '甘肃省',
'63': '青海省',
'64': '宁夏回族自治区',
'65': '新疆维吾尔自治区',
'71': '台湾省',
'81': '香港特别行政区',
'82': '澳门特别行政区',
'91': '国外'
};
// 校验正则15 位全数字18 位前17 数字 + 最后一位数字或 X
final reg15 = RegExp(r'^\d{15}$');
final reg18 = RegExp(r'^\d{17}[\dX]$');
String standardized = raw;
bool convertedFrom15 = false;
if (reg15.hasMatch(raw)) {
// 15 位 -> 转 18 位在第6位后插入 "19"),然后计算校验位
final prefix17 = raw.substring(0, 6) + '19' + raw.substring(6);
final checkChar = _calcCheckChar(prefix17);
standardized = prefix17 + checkChar;
convertedFrom15 = true;
} else if (reg18.hasMatch(raw)) {
standardized = raw;
} else {
return IDCardInfo(
raw: raw,
isValid: false,
error: '身份证格式不正确不是15位或18位',
checksumValid: false,
);
}
// 取出生日期
final birthStr = standardized.substring(6, 14); // YYYYMMDD
final year = int.tryParse(birthStr.substring(0, 4));
final month = int.tryParse(birthStr.substring(4, 6));
final day = int.tryParse(birthStr.substring(6, 8));
if (year == null || month == null || day == null) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '无法解析出生日期',
id18: standardized,
checksumValid: _verifyCheck(standardized),
);
}
// 校验日期是否真实存在(例如闰年等)
DateTime? birthDate;
try {
birthDate = DateTime(year, month, day);
// 额外检查同一天
if (birthDate.year != year || birthDate.month != month || birthDate.day != day) {
birthDate = null;
}
} catch (e) {
birthDate = null;
}
if (birthDate == null) {
return IDCardInfo(
raw: raw,
isValid: false,
error: '出生日期无效',
id18: standardized,
checksumValid: _verifyCheck(standardized),
);
}
// 年龄计算(按生日是否已过来算)
final now = DateTime.now();
int age = now.year - birthDate.year;
if (now.month < birthDate.month || (now.month == birthDate.month && now.day < birthDate.day)) {
age -= 1;
}
// 性别:第 17 位(索引 16为序列码的最后一位奇数男 偶数女
final seq = standardized.substring(14, 17); // 3 位序列号
final seqNum = int.tryParse(seq);
final gender = (seqNum != null && seqNum % 2 == 1) ? '' : '';
// 省份
final provinceCode = standardized.substring(0, 2);
final province = provinceMap[provinceCode] ?? '未知';
// 校验位验证
final checksumValid = _verifyCheck(standardized);
// 星座与生肖
final constellation = _calcConstellation(birthDate.month, birthDate.day);
final zodiac = _calcChineseZodiac(birthDate.year);
return IDCardInfo(
raw: raw,
isValid: true,
id18: standardized,
provinceCode: provinceCode,
province: province,
birthDate: birthDate,
birth: '${birthDate.year.toString().padLeft(4, '0')}-${birthDate.month.toString().padLeft(2, '0')}-${birthDate.day.toString().padLeft(2, '0')}',
age: age,
gender: gender,
checksumValid: checksumValid,
constellation: constellation,
zodiac: zodiac,
);
}
// ---- 辅助函数 ----
// 计算 17 位前缀的校验码(返回 '0'-'9' 或 'X'
String _calcCheckChar(String id17) {
// 权重
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
// 校验码映射 remainder -> char
const checkMap = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
int sum = 0;
for (var i = 0; i < 17; i++) {
final ch = id17[i];
final n = int.tryParse(ch) ?? 0;
sum += n * weights[i];
}
final mod = sum % 11;
return checkMap[mod];
}
// 验证完整 18 位身份证的校验位是否正确
bool _verifyCheck(String id18) {
if (id18.length != 18) return false;
final id17 = id18.substring(0, 17);
final expected = _calcCheckChar(id17);
final actual = id18[17].toUpperCase();
return expected == actual;
}
// 计算星座(西方)
String _calcConstellation(int month, int day) {
const names = [
'摩羯座', '水瓶座', '双鱼座', '白羊座', '金牛座', '双子座',
'巨蟹座', '狮子座', '处女座', '天秤座', '天蝎座', '射手座'
];
const startDays = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22];
// 月份从1开始
final idx = (month - 1);
if (day < startDays[idx]) {
return names[(idx + 11) % 12];
} else {
return names[idx];
}
}
// 计算生肖(中国农历生肖按公历年对照,简化算法)
String _calcChineseZodiac(int year) {
const zodiacs = [
'', '', '', '', '', '', '', '', '', '', '', ''
];
// 1900 是鼠年(可以用任意基准)
final idx = (year - 1900) % 12;
final i = idx < 0 ? (idx + 12) % 12 : idx;
return zodiacs[i];
}