银河

SKYIV STUDIO

  博客园 :: 首页 :: 博问 :: 闪存 :: :: :: 订阅 订阅 :: 管理 ::

中华人民共和国国家标准《GB 11643-1999 公民身份号码》的主要内容如下:

  • 范围:本标准规定了公民身份号码的编码对象、号码的结构和表现形式,使每个编码对象获得一个唯一的、不变的法定号码。
  • 编码对象:公民身份号码的编码对象是具有中华人民共和国国籍的公民。
  • 号码的结构:公民身份号码是特征组合码,由十七位数字本体码和一位数字检验码组成。排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
  • 地址码:表示编码对象常住户口所在县(市、旗、区)的行政区划代码,按 GB/T 2260 的规定执行。
  • 出生日期码:表示编码对象出生的年、月、日,按 GB/T 7408 的规定执行,年、月、日代码之间不用分隔符。
  • 顺序码:表示在同一地址码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配给女性。
  • 校验码:校验码采用 ISO 7064:1983, MOD 11-2 校验系统。

校验码的计算方法为:

  1. 将公民身份号码的第一位到第十七位数字分别乘以以下加权因子:7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2。
  2. 将这十七位数字和加权因子相乘的结果相加。
  3. 用加出来和除以11,得到的余数只可能是 0 1 2 3 4 5 6 7 8 9 10 这十一个数字,
  4. 对应的校验码为: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);
      }
    }
  }
}

这个程序的输出结果是:

123456            : 身份证号(123456)必须是(15)或者(18)位
123456950229002   : 身份证号(123456950229002)日期不合法
123456950228002   : OK
070843199507012145: 身份证号(070843199507012145)不能以零开头
37084-199507012145: 身份证号(37084-199507012145)除第(18)位外必须是数字
37084319950701214Y: 身份证号(37084319950701214Y)第(18)位必须是数字或者(x)或者(X)
370843179912312147: 身份证号(370843179912312147)日期太小
370843199507012145: 身份证号(370843199507012145)校验码错
15022319871109212X: OK

参考资料

posted on 2010-03-30 20:58  银河  阅读(3815)  评论(27编辑  收藏  举报