利用正则表达式来匹配居民身份证
大陆的居民身份证号码有两种:18位和15位,15位的身份证号码是老一代身份证号码。
18位和15位的区别在于两个部分:第一,18位号码的年份以4位计而15位号码的年份为2 位,如1987年在18位号码中为‘1987’而在15位号码中为‘87’,这里1987只是作为一个例子可能1987年及以后根本不存在15位号码;第二处不同在于18位号码的最后一位为数字校验码,15位号码没有数字校验位。
好吧,可能你还不知道身份证号码各个部分代表着什么,那么让我花点唇舌简略地介绍一下。
身份证号码总共有4个部分(15位号码只有3部分),从左向右分别为:第一部分有6位,为居民在办理身份证时户口所在地的地址码(什么是地址码?自己到统计局去找吧,地址如下:http://www.stats.gov.cn/tjbz/index.htm);第二部分有8位(15位号码为6位),为居民出生日期码;第三部分有3位,为数字顺序码,也就是同一天出生的人的一个排序,奇数代表男性而偶数代表女性;第四部分也就是最后一部分有1位,为数字校验码,此部分只有18位号码才有,关于数字校验码怎样计算得出,稍后会详述。
先举个例子吧,假设存在以下身份证号码:35052519870101888X(15位的话为350525870101888),用‘-’号将各部分区分如下:350525-19870101-888-X。其中,350525为地址码,没错,到统计局查询的结果是‘福建省永春县’——一个桃园胜境,算了,不废话;19870101为出生日期码,呵呵,1987年1月1日这一天出生的人肯定是有滴;888为顺序码,估计1987年1月1日第888个出生的人应该没有吧,倘若真的有,那真不是人,是神!!呵呵,估计我党也不会给神这个号码,不多说了,再多说可能要人神共怒了……好吧,那么X是什么呢?怎么有些人的身份证号码最后一位会突然冒出一个X呢,是这些人比较特殊吗?答案是:不是的,也算是吧,倘若有一种个位数等于10,这些人也不用在身份证号码的最后一位被不明不白地加上一个X了,究竟是怎么一回事且听我细细道来^_^
18位居民身份证号码最后一位——数字校验码的计算方法:
1. 将身份证号码的前17位数分别乘以以下系数:7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2;
2. 将以上分别相乘得到的结果相加;
3. 将以上相加的结果除以11,得出余数;
4. 以上得出的余数可能为0 - 10这11个数中的某一个数字。10是一个另类,因为我们的数字校验位只有1位显然需要一种替换方案,用1位将10换下,于是X派上用场了。注意了,数字校验位并不就是余数!!所得的11个余数:
0,1,2,3,4,5,6,7,8,9,10 的数字校验位分别为:
1,0,X,9,8,7,6,5,4,3,2
PS:通过以上的计算,现在应该非常清楚了,身份证号码的最后一位为X是因为在作校验时所得的余数是2,显然这些号码也没什么特别的吗。需要强调一下的是,X并不是英文字母哦,而是罗马数字X,记住,它不是字母而是数字,但是,呵呵,我们一般用英文大写字母X来表示罗马数字X,囧nz……
让我花点时间来校验以上的身份证号码:35052519870101888X是否正确吧,先声明倘若证实可用千万别拿去做假证,一切本人概不负责!
1.前17位分别乘以相应系数:3*7+5*9+0*10+5*5+2*8+5*4+1*2+9*1+8*6+7*3+0*7+1*9+0*10+1*5+8*8+8*4+8*2=333(假如没算错的话^_^)
2.将以上所得结果333除以11,得出余数:3
3.呵呵35052519870101888X这个号码不是有效的身份证号码,有效号码应该为350525198701018889
好了,知道了居民身份证号码各部分的意思后,我们终于可以动手寻求号码验证的解决方案了。
假设我们的系统并不知道输入号码之人的任何信息(如果系统还要求输入籍贯和出生日期,还可以进一步进行检验真伪喔^_^)
首先,地址码的前2位是省级的编号,到统计局去查询得知第1位非0,并且目前的范围为 1-9,9是国外的身份证号码。第2位的范围为0-7。统计局一般每一年都会公布一次更新的地址码,但对比多年来的地址码可以看出地址码基本上是不变的(要是变了,现有的身份证号码岂不是都没用了^_^)。我们的地址码的正则表达式可以这样写:/^[1-9][0-7]\d{4}/,其实,这么写不够精确,倘若要就目前的地址码写出精确的表达式,应该这么写/^((1[1-5])|(2[1-3])|(3[1-7])|(4[1-6])| (5[0-4])|(6[1-5])|71|(8[12])|91)\d{4}/,这样写显然精确多了,但表达式长了很多,当然为求精确表达式长一点是可以接受的,但是假如统计局修改了省级地址码,那么该表达式要根据实际情况稍作改动,所幸省级地址码应该是不会变的,呵呵,所以选择哪一种表达式都是可以的,我这就不写那么长的表达式了,所以选择/^[1-9][0-7]\d{4}/吧。
接下来就是日期了喔,呵呵,貌似之前写过的日期匹配可以借鉴来用一下,当时的那个表达式如下:
/^((((19|20)\d{2})-(0?[13-9]|1[012])-(0? [1-9]|[12]\d|30))|(((19|20)\d{2})-(0?[13578]|1[02])-31)|(((19|20) \d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|((((19|20)([13579][26]|[2468] [048]|0[48]))|(2000))-0?2-29))$/这个表达式可以匹配1900-2099年的日期,还支持闰年。
我们的表达式不需要匹配那么长的时间,能够匹配二十世纪的就够了,什么,不知道二十世纪?1900-1999总该知道吧^_^为什么匹配了这个范围就够了呢?去查一下居民身份证的历史吧,我敢打保票1900-1999的范围还太大了。至于二十一世纪的新一代,呵呵,就算他是2000年出生的,目前也就9岁(有些算法是8岁,囧),这些人的号码应该还打印在户口簿里,拿出手也不会产生什么作用,再说,二十一世纪的人也不屑于我目前所写的匹配^_^
好吧,废话太多了,开始匹配日期吧。稍微修改了以上表达式:
/((19\d{2}(0[13-9]|1[012])(0[1-9]|[12]\d|30))|(19\d{2}(0[13578]|1[02])31)|(19\d{2}02(0[1-9]|1\d|2[0-8]))|(19([13579][26]|[2468][048]|0[48])0229))/
正则表达式里没有计算验证的能力,所以对于顺序码,我们除了基本的匹配外无力于做什么,所以顺序码的表达式为:/\d{3}/
最后一位数字验证码——/(\d|X|x)?$/。之所以那么写是因为,最后一位可能值为数字或X,但有些人可能习惯将X写成小写的x,我们必须视为正确,而最后的?是为了与15位号码兼容,此时只需将15位号码的年份用4位表示即可用我们以下整合的表达式进行匹配了——
/^[1-9][0-7]\d{4}((19\d{2}(0[13-9]|1[012]) (0[1-9]|[12]\d|30))|(19\d{2}(0[13578]|1[02])31)|(19\d{2}02(0[1-9]|1 \d|2[0-8]))|(19([13579][26]|[2468][048]|0[48])0229))\d{3}(\d|X|x)?$/地址码精确一点的表达式:
/^((1[1-5])|(2[1-3])|(3[1-7])|(4[1-6])|(5[0-4])|(6[1-5])|71|(8[12])|91)\d{4}((19\d{2}(0[13-9]|1[012])(0[1-9]|[12]\d|30))|(19\d{2}(0[13578]|1[02])31)|(19\d{2}02(0[1-9]|1\d|2[0-8]))|(19([13579][26]|[2468][048]|0[48])0229))\d{3}(\d|X|x)?$/
关于号码的验证,以上已经给出算法