js校验身份证号码(15位、18位);15位与18位互转 ;且根据身份证号计算出性别、出生日期、年龄

5,113 阅读5分钟

身份证号码规则简介

早期的身份证号码即第一代身份证,为15位,1999年开始更名为公民身份证号码,即第二代身份证,为18位,且终身不变。

15位数升为18位数的一般规则是:第一步,在原15位数的第六位数后面补19 ,这样号码即为17位数;第二步,按照国家规定的统一公式计算出第十八位数即校验码。

公民身份号码是特征组合码,由前十七位数字本体码和最后一位数字校验码组成。排列顺序从左至右依次为六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。

身份证号码的组成(18位)

  • 地址码 身份证号码前六位数,表示编码对象常住户口所在省(直辖市、自治区)、地(市),以及县(市、旗、区)的行政区划代码,按照GB/T2260的规定执行。其中:前一、二位数字表示所在省(直辖市、自治区)的代码;第三、四位数字表示所在地(市)的代码;第五、六位数字表示:所在县(市、旗、区)的代码。
  • 出生日期码 身份证号码第七位至十四位:表示编码对象出生的年、月、日,按GB/T7408的规定执行,年、月、日代码之间不用分隔符。
  • 顺序码 身份证号码第十五位至十七位:表示在同一地址码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号,第十七位的奇数分配给男性,偶数分配给女性。
  • 校验码 第十八位数:根据本体码,通过采用ISO 7064:1983,MOD 11-2校验码系统计算出校验码。算法可参考下文。前面有提到数字校验码,我们知道校验码也有X的,实质上为罗马字符X,相当于10

15位与18位身份证号码差异

  1. 出生日期码:15位身份证号码中出生日期码为4位,其中年份代码仅有2位,如590328,代表1959年生。

  2. 校验码:15位身份证号码中无校验位。

校验码算法

将本体码各位数字乘以对应加权因子并求和,除以11得到余数,根据余数通过校验码对照表查得校验码。

加权因子:

d58583a66659f81e4f8c19f17e3fe059_b

校验码:

c1e027e8380273be48eeae96d9281e2a_b

算法举例:

本体码为:11010519491231002

第一步: 各位数与对应加权因子乘积求和1 * 7+1 * 9 + 0 * 10 + 1 * 5+ *** = 167

第二步:对求和进行除11得余数167%11=2

第三步:根据余数2对照校验码得X

因此完整身份证号为:11010519491231002X

编码规则的应用

如上所述,身份证号码是基于一定的规则的,从身份证号码中可以挖掘以下信息:

  1. 身份证长度验证:身份证长度仅存在15位或18位两种长度;
  2. 身份证输入正确性验证:如身份证号为18位,可根据本体码与校验码得关系验证身份证号码输入有误,需要指出的是,验证通过并不代表身份证号码的真实性;
  3. 获得户籍注册地信息:大部分情况下,该地即为出身地或籍贯。对应时需留意行政区划代码的更新迭代;
  4. 出生年月及其正确性;
  5. 性别:顺序码中奇数为男性,偶数位为女性;
  6. 根据18位与15位身份证号码编码规则及校验码算法,将15位身份证号码转换为18位身份证号码;
  7. 小心坑:因校验码为X,实际上有大小写输入不同,请注意兼容或统一。

js代码封装

// idCard.js文件
export default {
  /*省,直辖市代码表*/
  provinceAndCitys: {
    '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': '国外',
  },

  /*每位加权因子*/
  powers: [
    '7',
    '9',
    '10',
    '5',
    '8',
    '4',
    '2',
    '1',
    '6',
    '3',
    '7',
    '9',
    '10',
    '5',
    '8',
    '4',
    '2',
  ],

  /*第18位校检码*/
  parityBit: ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'],

  /*性别*/
  genders: { male: '男', female: '女' },

  /*校验地址码*/
  checkAddressCode: function(addressCode) {
    let check = /^[1-9]\d{5}$/.test(addressCode);
    if (!check) return false;
    if (this.provinceAndCitys[addressCode.substring(0, 2)]) {
      return true;
    } else {
      return false;
    }
  },

  /*校验日期码*/
  checkBirthDayCode: function(birDayCode) {
    let check = /^[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))$/.test(
      birDayCode
    );
    if (!check) return false;
    let yyyy = parseInt(birDayCode.substring(0, 4), 10);
    let mm = parseInt(birDayCode.substring(4, 6), 10);
    let dd = parseInt(birDayCode.substring(6), 10);
    let xdata = new Date(yyyy, mm - 1, dd);
    if (xdata > new Date()) {
      return false; //生日不能大于当前日期
    } else if (
      xdata.getFullYear() == yyyy &&
      xdata.getMonth() == mm - 1 &&
      xdata.getDate() == dd
    ) {
      return true;
    } else {
      return false;
    }
  },

  /*计算校检码*/
  getParityBit: function(idCardNo) {
    let id17 = idCardNo.substring(0, 17);
    /*加权 */
    let power = 0;
    for (let i = 0; i < 17; i++) {
      power += parseInt(id17.charAt(i), 10) * parseInt(this.powers[i]);
    }
    /*取模*/
    let mod = power % 11;
    return this.parityBit[mod];
  },

  /*验证校检码*/
  checkParityBit: function(idCardNo) {
    let parityBit = idCardNo.charAt(17).toUpperCase();
    if (this.getParityBit(idCardNo) == parityBit) {
      return true;
    } else {
      return false;
    }
  },

  /*校验15位或18位的身份证号码*/
  checkIdCardNo: function(idCardNo) {
    //15位和18位身份证号码的基本校验
    let check = /^\d{15}|(\d{17}(\d|x|X))$/.test(idCardNo);
    if (!check) return false;
    //判断长度为15位或18位
    if (idCardNo.length == 15) {
      return this.check15IdCardNo(idCardNo);
    } else if (idCardNo.length == 18) {
      return this.check18IdCardNo(idCardNo);
    } else {
      return false;
    }
  },

  //校验15位的身份证号码
  check15IdCardNo: function(idCardNo) {
    //15位身份证号码的基本校验
    let check = /^[1-9]\d{7}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}$/.test(
      idCardNo
    );
    if (!check) return false;
    //校验地址码
    let addressCode = idCardNo.substring(0, 6);
    check = this.checkAddressCode(addressCode);
    if (!check) return false;
    let birDayCode = '19' + idCardNo.substring(6, 12);
    //校验日期码
    return this.checkBirthDayCode(birDayCode);
  },

  //校验18位的身份证号码
  check18IdCardNo: function(idCardNo) {
    //18位身份证号码的基本格式校验
    let check = /^[1-9]\d{5}[1-9]\d{3}((0[1-9])|(1[0-2]))((0[1-9])|([1-2][0-9])|(3[0-1]))\d{3}(\d|x|X)$/.test(
      idCardNo
    );
    if (!check) return false;
    //校验地址码
    let addressCode = idCardNo.substring(0, 6);
    check = this.checkAddressCode(addressCode);
    if (!check) return false;
    //校验日期码
    let birDayCode = idCardNo.substring(6, 14);
    check = this.checkBirthDayCode(birDayCode);
    if (!check) return false;
    //验证校检码
    return this.checkParityBit(idCardNo);
  },

  formateDateCN: function(day) {
    let yyyy = day.substring(0, 4);
    let mm = day.substring(4, 6);
    let dd = day.substring(6);
    return yyyy + '-' + mm + '-' + dd;
  },

  //获取性别、出生日期、年龄信息
  getIdCardInfo: function(idCardNo) {
    let idCardInfo = {
      gender: '', //性别
      birthday: '', // 出生日期(yyyy-mm-dd)
      age: '',
    };
    if (idCardNo.length == 15) {
      let aday = '19' + idCardNo.substring(6, 12);
      idCardInfo.birthday = this.formateDateCN(aday);
      if (parseInt(idCardNo.charAt(14)) % 2 == 0) {
        idCardInfo.gender = this.genders.female;
      } else {
        idCardInfo.gender = this.genders.male;
      }
    } else if (idCardNo.length == 18) {
      let aday = idCardNo.substring(6, 14);
      idCardInfo.birthday = this.formateDateCN(aday);
      if (parseInt(idCardNo.charAt(16)) % 2 == 0) {
        idCardInfo.gender = this.genders.female;
      } else {
        idCardInfo.gender = this.genders.male;
      }
    }
    idCardInfo.age = this.calculationAge(idCardInfo.birthday);
    return idCardInfo;
  },
  
   /**
   * 通过出生日期计算年龄
   * @param dateStr yyyy-MM-DD
   */
  calculationAge: function(dateStr){
    var r = dateStr.match(/^(\d{1,4})(-|\/)(\d{1,2})\2(\d{1,2})$/);
    if (r == null) return false;
    var d = new Date(r[1], r[3] - 1, r[4]);
    if (
      d.getFullYear() == r[1] &&
      d.getMonth() + 1 == r[3] &&
      d.getDate() == r[4]
    ) {
        var Y = new Date().getFullYear()
        var M = this.zero0(new Date().getMonth() + 1)
        var D = this.zero0(new Date().getDate())
        if (M > r[3] || (M === r[3] && D >= r[4])) return Y - r[1]
        return Y - r[1] - 1
    }
    return false;
  },
  
  zero0: function(num) {
     return `${num < 10 ? '0'+num : num}`
   },

  /*18位转15位*/
  getId15: function(idCardNo) {
    if (idCardNo.length == 15) {
      return idCardNo;
    } else if (idCardNo.length == 18) {
      return idCardNo.substring(0, 6) + idCardNo.substring(8, 17);
    } else {
      return null;
    }
  },

  /*15位转18位*/
  getId18: function(idCardNo) {
    if (idCardNo.length == 15) {
      let id17 = idCardNo.substring(0, 6) + '19' + idCardNo.substring(6);
      let parityBit = this.getParityBit(id17);
      return id17 + parityBit;
    } else if (idCardNo.length == 18) {
      return idCardNo;
    } else {
      return null;
    }
  },
};

使用:

import idCardTool from './idCard.js';

// 校验身份证
if (!idCardTool.checkIdCardNo(idCard)) {
   console.log('请填写正确的证件号码')
}

// 获取身份证的性别、年龄、出生日期
const cardInfo = idCardTool.getIdCardInfo(idCard)
console.log('性别', cardInfo.gender, '年龄',cardInfo.age, '出生日期', cardInfo.birthday)

附件:测试demo

参考文章

身份证号码编码规则及其应用