iOS身份證判斷正則加算法

A simple Id card validation rules written in pure swift, lightweight but powerful.

????中文介紹

最近項目中用到了判斷身份證合法性,本來想著網上正則一大堆,就隨便復制粘貼了一個,誰曾想遇到一個身份證號帶X的測試,測試說把X換成數字,一定不是正確的身份證號,你這樣寫不對,很早以前就聽說身份證號正則只能判斷格式是否正確,而對于身份證號正確性需要用算法計算出來。

Requirements

  • iOS 8.0+
  • Xcode 8
  • Swift 3.0

GitHub

OCDemo

SwiftDemo

看看下面的身份證常識,再看代碼的話,你的邏輯一下就明了了。

身份證常識

我國的身份證號分為15位和18位兩種。身份證是國民的身份編號,編號是有一定規律的。
居民身份證號碼,根據〖中華人民共和國國家標準 GB 11643-1999〗中有關公民身份號碼的規定,公民身份號碼是特征組合碼,由十七位數字本體碼和一位數字校驗碼組成。排列順序從左至右依次為:六位數字地址碼,八位數字出生日期碼,三位數字順序碼和一位數字校驗碼。 居民身份證是國家法定的證明公民個人身份的有效證件。

結構和形式

1號碼的結構

公民身份號碼是特征組合碼,由十七位數字本體碼和一位校驗碼組成。排列順序從左至右依次為:六位數字地址碼,八位數字出生日期碼,三位數字順序碼和一位數字校驗碼。

2.地址碼

表示編碼對象常住戶口所在縣(市、旗、區)的行政區劃代碼,按GB/T2260的規定執行。

3.出生日期碼

表示編碼對象出生的年、月、日,按GB/T7408的規定執行,年、月、日代碼之間不用分隔符。

4.順序碼

表示在同一地址碼所標識的區域范圍內,對同年、同月、同日出生的人編定的順序號,順序碼的奇數分配給男性,偶數分配給女性。

5.校驗碼

根據前面十七位數字碼,按照ISO7064:1983.MOD11-2校驗碼計算出來的檢驗碼。

6.地址碼

華北地區: 北京市|110000,天津市|120000,河北省|130000,山西省|140000,內蒙古自治區|150000,
東北地區: 遼寧省|210000,吉林省|220000,黑龍江省|230000,
華東地區: 上海市|310000,江蘇省|320000,浙江省|330000,安徽省|340000,福建省|350000,江西省|360000,山東省|370000,
華中地區: 河南省|410000,湖北省|420000,湖南省|430000
華南地區: 廣東省|440000,廣西壯族自治區|450000,海南省|460000,
西南地區: 重慶市|500000,四川省|510000,貴州省|520000,云南省|530000,西藏自治區|540000,
西北地區: 陜西省|610000,甘肅省|620000,青海省|630000,寧夏回族自治區|640000,新疆維吾爾自治區|650000,
特別地區:臺灣地區(886)|710000,香港特別行政區(852)|810000,澳門特別行政區(853)|820000
   
    中國大陸居民身份證號碼中的地址碼的數字編碼規則為:
    第一、二位表示省(自治區、直轄市、特別行政區)。
   
    第三、四位表示市(地級市、自治州、盟及國家直轄市所屬市轄區和縣的匯總碼)。其中,01-20,51-70表示省直轄市;21-50表示地區(自治州、盟)。
   
    第五、六位表示縣(市轄區、縣級市、旗)。01-18表示市轄區或地區(自治州、盟)轄縣級市;21-80表示縣(旗);81-99表示省直轄縣級市。

7.生日期碼

身份證號碼第七位到第十四位)表示編碼對象出生的年、月、日,其中年份用四位數字表示,年、月、日之間不用分隔符。例如:1981年05月11日就用19810511表示。

8.順序碼

身份證號碼第十五位到十七位)地址碼所標識的區域范圍內,對同年、月、日出生的人員編定的順序號。其中第十七位奇數分給男性,偶數分給女性

9.校驗碼

作為尾號的校驗碼,是由號碼編制單位按統一的公式計算出來的,如果某人的尾號是0-9,都不會出現X,但如果尾號是10,那么就得用X來代替,因為如果用10做尾號,那么此人的身份證就變成了19位,而19位的號碼違反了國家標準,并且中國的計算機應用系統也不承認19位的身份證號碼。Ⅹ是羅馬數字的10,用X來代替10,可以保證公民的身份證符合國家標準。

10.身份證校驗碼的計算方法

1、將前面的身份證號碼17位數分別乘以不同的系數。從第一位到第十七位的系數分別為:7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2。
2、將這17位數字和系數相乘的結果相加。
3、用加出來和除以11,看余數是多少?
4、余數只可能有0-1-2-3-4-5-6-7-8-9-10這11個數字。其分別對應的最后一位身份證的號碼為1-0-X-9-8-7-6-5-4-3-2。(即余數0對應1,余數1對應0,余數2對應X...)
5、通過上面得知如果余數是3,就會在身份證的第18位數字上出現的是9。如果對應的數字是2,身份證的最后一位號碼就是羅馬數字x。

下面直接粘貼代碼

OC版本的

 -(BOOL)validateIDCardNumber:(NSString *)value {
     
     value = [value stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
     NSInteger length =0;
     if (!value) {
     return NO;
     }else {
     length = value.length;
     //不滿足15位和18位,即身份證錯誤
     if (length !=15 && length !=18) {
     return NO;
     }
     }
     // 省份代碼
     NSArray *areasArray = @[@"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"];
     
     // 檢測省份身份行政區代碼
     NSString *valueStart2 = [value substringToIndex:2];
     BOOL areaFlag =NO; //標識省份代碼是否正確
     for (NSString *areaCode in areasArray) {
     if ([areaCode isEqualToString:valueStart2]) {
     areaFlag =YES;
     break;
     }
     }
     
     if (!areaFlag) {
     return NO;
     }
     
     NSRegularExpression *regularExpression;
     NSUInteger numberofMatch;
     
     int year =0;
     //分為15位、18位身份證進行校驗
     switch (length) {
     case 15:
     //獲取年份對應的數字
     year = [value substringWithRange:NSMakeRange(6,2)].intValue +1900;
     
     if (year %4 ==0 || (year %100 ==0 && year %4 ==0)) {
     //創建正則表達式 NSRegularExpressionCaseInsensitive:不區分字母大小寫的模式
     regularExpression = [[NSRegularExpression alloc]initWithPattern:@"^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}$"
     options:NSRegularExpressionCaseInsensitive error:nil];//測試出生日期的合法性
     }else {
     regularExpression = [[NSRegularExpression alloc]initWithPattern:@"^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}$"
     options:NSRegularExpressionCaseInsensitive error:nil];//測試出生日期的合法性
     }
     //使用正則表達式匹配字符串 NSMatchingReportProgress:找到最長的匹配字符串后調用block回調
     numberofMatch = [regularExpression numberOfMatchesInString:value
     options:NSMatchingReportProgress
     range:NSMakeRange(0, value.length)];
     
     if(numberofMatch >0) {
     return YES;
     }else {
     return NO;
     }
     case 18:
     year = [value substringWithRange:NSMakeRange(6,4)].intValue;
     if (year %4 ==0 || (year %100 ==0 && year %4 ==0)) {
     regularExpression = [[NSRegularExpression alloc]initWithPattern:@"^((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|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}02(0[1-9]|1\\d|2[0-8]))|((19|20)([13579][26]|[2468][048]|0[048])0229))\\d{3}(\\d|X|x)?$" options:NSRegularExpressionCaseInsensitive error:nil];//測試出生日期的合法性
     }else {
     regularExpression = [[NSRegularExpression alloc]initWithPattern:@"^((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|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}02(0[1-9]|1\\d|2[0-8]))|((19|20)([13579][26]|[2468][048]|0[048])0229))\\d{3}(\\d|X|x)?$" options:NSRegularExpressionCaseInsensitive error:nil];//測試出生日期的合法性
     }
     numberofMatch = [regularExpression numberOfMatchesInString:value
     options:NSMatchingReportProgress
     range:NSMakeRange(0, value.length)];
     
     
     if(numberofMatch >0) {
     //1:校驗碼的計算方法 身份證號碼17位數分別乘以不同的系數。從第一位到第十七位的系數分別為:7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2。將這17位數字和系數相乘的結果相加。
     
     int S = [value substringWithRange:NSMakeRange(0,1)].intValue*7 + [value substringWithRange:NSMakeRange(10,1)].intValue *7 + [value substringWithRange:NSMakeRange(1,1)].intValue*9 + [value substringWithRange:NSMakeRange(11,1)].intValue *9 + [value substringWithRange:NSMakeRange(2,1)].intValue*10 + [value substringWithRange:NSMakeRange(12,1)].intValue *10 + [value substringWithRange:NSMakeRange(3,1)].intValue*5 + [value substringWithRange:NSMakeRange(13,1)].intValue *5 + [value substringWithRange:NSMakeRange(4,1)].intValue*8 + [value substringWithRange:NSMakeRange(14,1)].intValue *8 + [value substringWithRange:NSMakeRange(5,1)].intValue*4 + [value substringWithRange:NSMakeRange(15,1)].intValue *4 + [value substringWithRange:NSMakeRange(6,1)].intValue*2 + [value substringWithRange:NSMakeRange(16,1)].intValue *2 + [value substringWithRange:NSMakeRange(7,1)].intValue *1 + [value substringWithRange:NSMakeRange(8,1)].intValue *6 + [value substringWithRange:NSMakeRange(9,1)].intValue *3;
     
     //2:用加出來和除以11,看余數是多少?余數只可能有0-1-2-3-4-5-6-7-8-9-10這11個數字
     int Y = S %11;
     NSString *M =@"F";
     NSString *JYM =@"10X98765432";
     M = [JYM substringWithRange:NSMakeRange(Y,1)];// 3:獲取校驗位
     
     NSString *lastStr = [value substringWithRange:NSMakeRange(17,1)];
     
     NSLog(@"%@",M);
     NSLog(@"%@",[value substringWithRange:NSMakeRange(17,1)]);
     //4:檢測ID的校驗位
     if ([lastStr isEqualToString:@"x"]) {
     if ([M isEqualToString:@"X"]) {
     return YES;
     }else{
     
     return NO;
     }
     }else{
     
     if ([M isEqualToString:[value substringWithRange:NSMakeRange(17,1)]]) {
     return YES;
     }else {
     return NO;
     }
     
     }
     
     
     }else {
     return NO;
     }
     default:
     return NO;
     }
     }

Swift版本的

func isTrueIDNumber(text:String) -> Bool{
     
     var value = text
     
     value = value.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
     
     var length : Int = 0
     
     length = value.characters.count
     
     if length != 15 && length != 18{
     //不滿足15位和18位,即身份證錯誤
     return false
     }
     
     // 省份代碼
     let areasArray = ["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"]
     
     // 檢測省份身份行政區代碼
     let index = value.index(value.startIndex, offsetBy: 2)
     let valueStart2 = value.substring(to: index)
     
     //標識省份代碼是否正確
     var areaFlag = false
     
     for areaCode in areasArray {
     
     if areaCode == valueStart2 {
     areaFlag = true
     break
     }
     }
     
     if !areaFlag {
     return false
     }
     
     var regularExpression : NSRegularExpression?
     
     var numberofMatch : Int?
     
     var year = 0
     
     switch length {
     case 15:
     
     //獲取年份對應的數字
     let valueNSStr = value as NSString
     
     let yearStr = valueNSStr.substring(with: NSRange.init(location: 6, length: 2)) as NSString
     
     year = yearStr.integerValue + 1900
     
     if year % 4 == 0 || (year % 100 == 0 && year % 4 == 0) {
     //創建正則表達式 NSRegularExpressionCaseInsensitive:不區分字母大小寫的模式
     //測試出生日期的合法性
     regularExpression = try! NSRegularExpression.init(pattern: "^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}$", options: NSRegularExpression.Options.caseInsensitive)
     }else{
     
     //測試出生日期的合法性
     regularExpression = try! NSRegularExpression.init(pattern: "^[1-9][0-9]{5}[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|3[0-1])|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|1[0-9]|2[0-8]))[0-9]{3}$", options: NSRegularExpression.Options.caseInsensitive)
     }
     
     numberofMatch = regularExpression?.numberOfMatches(in: value, options: NSRegularExpression.MatchingOptions.reportProgress, range: NSRange.init(location: 0, length: value.characters.count))
     
     if numberofMatch! > 0 {
     return true
     }else{
     
     return false
     }
     
     case 18:
     
     let valueNSStr = value as NSString
     
     let yearStr = valueNSStr.substring(with: NSRange.init(location: 6, length: 4)) as NSString
     
     year = yearStr.integerValue
     
     if year % 4 == 0 || (year % 100 == 0 && year % 4 == 0) {
     
     //測試出生日期的合法性
     regularExpression = try! NSRegularExpression.init(pattern: "^((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|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}02(0[1-9]|1\\d|2[0-8]))|((19|20)([13579][26]|[2468][048]|0[048])0229))\\d{3}(\\d|X|x)?$", options: NSRegularExpression.Options.caseInsensitive)
     
     }else{
     
     //測試出生日期的合法性
     regularExpression = try! NSRegularExpression.init(pattern: "^((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|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}02(0[1-9]|1\\d|2[0-8]))|((19|20)([13579][26]|[2468][048]|0[048])0229))\\d{3}(\\d|X|x)?$", options: NSRegularExpression.Options.caseInsensitive)
     }
     
     numberofMatch = regularExpression?.numberOfMatches(in: value, options: NSRegularExpression.MatchingOptions.reportProgress, range: NSRange.init(location: 0, length: value.characters.count))
     
     if numberofMatch! > 0 {
     
     let a = getStringByRangeIntValue(Str: valueNSStr, location: 0, length: 1) * 7
     
     let b = getStringByRangeIntValue(Str: valueNSStr, location: 10, length: 1) * 7
     
     let c = getStringByRangeIntValue(Str: valueNSStr, location: 1, length: 1) * 9
     
     let d = getStringByRangeIntValue(Str: valueNSStr, location: 11, length: 1) * 9
     
     let e = getStringByRangeIntValue(Str: valueNSStr, location: 2, length: 1) * 10
     
     let f = getStringByRangeIntValue(Str: valueNSStr, location: 12, length: 1) * 10
     
     let g = getStringByRangeIntValue(Str: valueNSStr, location: 3, length: 1) * 5
     
     let h = getStringByRangeIntValue(Str: valueNSStr, location: 13, length: 1) * 5
     
     let i = getStringByRangeIntValue(Str: valueNSStr, location: 4, length: 1) * 8
     
     let j = getStringByRangeIntValue(Str: valueNSStr, location: 14, length: 1) * 8
     
     let k = getStringByRangeIntValue(Str: valueNSStr, location: 5, length: 1) * 4
     
     let l = getStringByRangeIntValue(Str: valueNSStr, location: 15, length: 1) * 4
     
     let m = getStringByRangeIntValue(Str: valueNSStr, location: 6, length: 1) * 2
     
     let n = getStringByRangeIntValue(Str: valueNSStr, location: 16, length: 1) * 2
     
     let o = getStringByRangeIntValue(Str: valueNSStr, location: 7, length: 1) * 1
     
     let p = getStringByRangeIntValue(Str: valueNSStr, location: 8, length: 1) * 6
     
     let q = getStringByRangeIntValue(Str: valueNSStr, location: 9, length: 1) * 3
     
     let S = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q
     
     let Y = S % 11
     
     var M = "F"
     
     let JYM = "10X98765432"
     
     M = (JYM as NSString).substring(with: NSRange.init(location: Y, length: 1))
     
     let lastStr = valueNSStr.substring(with: NSRange.init(location: 17, length: 1))
     
     if lastStr == "x" {
     if M == "X" {
     return true
     }else{
     
     return false
     }
     }else{
     
     if M == lastStr {
     return true
     }else{
     
     return false
     }
     }
     }else{
     
     return false
     }
     
     default:
     return false
     }
     }
     
     func getStringByRangeIntValue(Str : NSString,location : Int, length : Int) -> Int{
     
     let a = Str.substring(with: NSRange(location: location, length: length))
     
     let intValue = (a as NSString).integerValue
     
     return intValue
     }

推薦文章

iPhone X 適配(Swift篇)

iOS身份證判斷正則加算法

RN環境搭建及與原生交互

RxSwift使用手冊

RxSwift日常項目使用(持續更新。。。)

比較RAC和RxSwift

作者

偽文藝的程序員

總結

如果你感覺有用的話,拿走,點個喜歡就OK

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375