中华人民共和国国家标准《GB 11643-1999 公民身份号码》的主要内容如下:
- 范围:本标准规定了公民身份号码的编码对象、号码的结构和表现形式,使每个编码对象获得一个唯一的、不变的法定号码。
- 编码对象:公民身份号码的编码对象是具有中华人民共和国国籍的公民。
- 号码的结构:公民身份号码是特征组合码,由十七位数字本体码和一位数字检验码组成。排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
- 地址码:表示编码对象常住户口所在县(市、旗、区)的行政区划代码,按 GB/T 2260 的规定执行。
- 出生日期码:表示编码对象出生的年、月、日,按 GB/T 7408 的规定执行,年、月、日代码之间不用分隔符。
- 顺序码:表示在同一地址码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配给女性。
- 校验码:校验码采用 ISO 7064:1983, MOD 11-2 校验系统。
校验码的计算方法为:
- 将公民身份号码的第一位到第十七位数字分别乘以以下加权因子:7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2。
- 将这十七位数字和加权因子相乘的结果相加。
- 用加出来和除以11,得到的余数只可能是 0 1 2 3 4 5 6 7 8 9 10 这十一个数字,
- 对应的校验码为:1 0 X 9 8 7 6 5 4 3 2。
公民身份证号码在很多程序中都会用到。所以,我写了一个助手类,如下所示:
using System; using System.Globalization; using System.Diagnostics; namespace Skyiv.Ben.Common { /// <summary> /// 中华人民共和国居民身份证 /// </summary> public sealed class Idcard { [Flags] public enum VerifyMode { None = 0, CheckNumber = 1, Date = 2, Full = CheckNumber | Date } public VerifyMode Mode { get; private set; } public DateTime MaxDate { get; private set; } public static readonly Idcard None = new Idcard(VerifyMode.None); public static readonly Idcard CheckNumber = new Idcard(VerifyMode.CheckNumber); public static readonly Idcard Date = new Idcard(VerifyMode.Date); public static readonly Idcard Full = new Idcard(VerifyMode.Full); static readonly byte[] weight = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 }; static readonly string code = "10X98765432"; static readonly DateTime MinDate = new DateTime(1800, 1, 1); public Idcard(VerifyMode mode) : this(mode, DateTime.MaxValue) { } public Idcard(VerifyMode mode, DateTime maxDate) { Mode = mode; MaxDate = maxDate; } public long String2Number(string s) { long n; string msg; if (!TryString2Number(s, out n, out msg)) throw new ArgumentOutOfRangeException(msg, (Exception)null); return n; } public string Number2String(long n) { return (n == 0) ? "" : (Math.Abs(n).ToString() + ((n < 0) ? "X" : "")); } public bool TryString2Number(string s, out long n, out string msg) { if ((msg = TryString2Number(s, out n)) == null) return true; msg = "身份证号(" + s + ")" + msg; return false; } public string Convert15to18(string oldId) { string newId; if (!TryConvert15to18(oldId, out newId)) throw new ArgumentOutOfRangeException(newId, (Exception)null); return newId; } public bool TryConvert15to18(string oldId, out string newId) { if (oldId != null && oldId.Length == 18 && oldId.StartsWith("000")) oldId = oldId.Substring(3); long n; if (!TryString2Number(oldId, out n, out newId)) return false; newId = oldId; if (oldId.Length == 18) return true; Debug.Assert(oldId.Length == 15); newId = oldId.Substring(0, 6) + GetDateStr(oldId) + oldId.Substring(12); newId += GetCheckNumber(newId); return true; } string TryString2Number(string s, out long n) { n = 0; if (s == null) return "不能为空"; string msg; if ((msg = VerifyBase(s)) != null) return msg; if (Has(VerifyMode.CheckNumber) && (msg = VerifyCheckNumber(s)) != null) return msg; if (Has(VerifyMode.Date) && (msg = VerifyDate(s)) != null) return msg; Debug.Assert(s.Length == 15 || s.Length == 18); if (s.Length == 18 && !char.IsDigit(s, 17)) s = "-" + s.Substring(0, 17); n = long.Parse(s); return msg; } string VerifyBase(string s) { if (s.Length != 15 && s.Length != 18) return "必须是(15)或者(18)位"; if (s[0] == '0') return "不能以零开头"; for (int i = 0; i < s.Length; i++) if (i == 17 && !char.IsDigit(s, i) && s[i] != 'x' && s[i] != 'X') return "第(18)位必须是数字或者(x)或者(X)"; else if (i != 17 && !char.IsDigit(s, i)) return "除第(18)位外必须是数字"; return null; } string VerifyCheckNumber(string s) { Debug.Assert(s.Length == 15 || s.Length == 18); if (s.Length != 18 || char.ToUpper(s[17]) == GetCheckNumber(s)) return null; return "校验码错"; } string VerifyDate(string s) { Debug.Assert(s.Length == 15 || s.Length == 18); var dateStr = GetDateStr(s); DateTime date; if (!DateTime.TryParseExact(dateStr, "yyyyMMdd", CultureInfo.InvariantCulture, DateTimeStyles.None, out date)) return "日期不合法"; if (date < MinDate) return "日期太小"; if (date > MaxDate) return "日期太大"; return null; } string GetDateStr(string s) { var dateStr = (s.Length == 18) ? "" : ((int.Parse(s.Substring(12, 3)) > 995) ? "18" : "19"); return dateStr + s.Substring(6, (s.Length == 18) ? 8 : 6); } char GetCheckNumber(string s) { Debug.Assert(s.Length == 17 || s.Length == 18); var sum = 0; for (var i = 0; i < weight.Length; i++) sum += weight[i] * (s[i] - '0'); return code[sum % 11]; } bool Has(VerifyMode mode) { return (Mode & mode) == mode; } } }
示例程序如下所示:
using System; using Skyiv.Ben.Common; namespace Skyiv.Ben { class Test { static void Main() { string[] idcards = { "123456", "123456950229002", "123456950228002", "070843199507012145", "37084-199507012145", "37084319950701214Y", "370843179912312147", "370843199507012145", "15022319871109212X", }; long n; string msg; foreach (var idcard in idcards) { var valid = Idcard.Full.TryString2Number(idcard, out n, out msg); Console.WriteLine("{0,-18}: {1}", idcard, valid ? "OK" : msg); } } } }
这个程序的输出结果是: