export const isSAIdNumber = (val: string | string, args: string[]) => new Promise((resolve, reject) => resolve( SAIDCheck(val) ) ); /// check source for https://www.checkid.co.za/ // 1 - 6 = Date of birth // 7 = Gender identification (0 - 4 is female, 5 - 9 is male) // 8 - 10 = Sequential number (not used) // 11 = Citizensip (0 = SA, 1 = non SA, 2 = refugee) // 12 = race // 13 = verification number const checkNumber = (num) => { let result = { dob: null, age: null, gender: null, citizenship: null, race: null, error: null, isValid: null }; let substrings = { dob: num.substring(0, 6), gender: num.substring(6, 7), citizenship: num.substring(10, 11), race: num.substring(11, 12) } // DOB & Age let id = num; let yy = id.substring(0, 2); let cc = '19'; const thisYear = new Date().getFullYear(); if ( parseInt(yy) <= thisYear - 2000 ) { cc = '20'; } let ccyy = cc + yy; let mm = id.substring(2, 4); let dd = id.substring(4, 6); let dob = ccyy + '-' + mm + '-' + dd; result.dob = new Date( `${ccyy}/${mm}/${dd}` ); result.age = thisYear - result.dob.getFullYear(); // Check for valid date if (isNaN(result.age)) { result.error = 'Invalid ID Number - gender could not be determined'; return result; } // Gender let genderDigit = parseInt(substrings.gender); if (genderDigit >= 0 && genderDigit <= 4) result.gender = 'Female'; else if (genderDigit >= 5 && genderDigit <= 9) result.gender = 'Male'; else { result.error = 'Invalid ID Number - gender could not be determined'; return result; } // Citizenship let citizenshipDigit = parseInt(substrings.citizenship); switch (citizenshipDigit) { case 0: result.citizenship = 'SA Citizen'; break; case 1: result.citizenship = 'Non-SA Citizen'; break; case 2: result.citizenship = 'Refugee'; break; default: result.error = 'Invalid ID Number - citizenship could not be determined'; return result; } // Race let raceDigit = parseInt(substrings.race); switch (raceDigit) { case 8: result.race = 'White'; case 9: result.race = 'Black'; default: result.race = null; } // Date of month // Check digit - https://web.archive.org/web/20070808182936/http://geekswithblogs.net/willemf/archive/2005/10/30/58561.aspx result.isValid = SAIDCheck(num); return result; }; //private method CalcSumOfString const CalcSumOfString = (ValueToSum) => { let lenghtOfString = ValueToSum.length; let sumOfString = 0; for (let i = 0; i < lenghtOfString; i++) { sumOfString += parseInt(ValueToSum.substr(i, 1)); } return sumOfString; } //private method SAIDCheck const SAIDCheck = (IdNumber) => { let d1 = 0; let d2 = 0; let d3 = 0; let d4 = 0; let d5 = 0; let d6 = 0; let d7 = 0; let d8 = 0; let d9 = 0; let d10 = 0; let d11 = 0; let d12 = 0; let d13 = 0; let evsum = 0; let odsum = 0; let evnum1 = 0; let evnum2 = 0; let evnum3 = 0; let evnum4 = 0; let evnum5 = 0; let evnum6 = 0; let checkDigit = 0; if (IdNumber.length == 13) { d1 = parseInt(IdNumber.substr(0, 1), 10); d2 = parseInt(IdNumber.substr(1, 1), 10); d3 = parseInt(IdNumber.substr(2, 1), 10); d4 = parseInt(IdNumber.substr(3, 1), 10); d5 = parseInt(IdNumber.substr(4, 1), 10); d6 = parseInt(IdNumber.substr(5, 1), 10); d7 = parseInt(IdNumber.substr(6, 1), 10); d8 = parseInt(IdNumber.substr(7, 1), 10); d9 = parseInt(IdNumber.substr(8, 1), 10); d10 = parseInt(IdNumber.substr(9, 1), 10); d11 = parseInt(IdNumber.substr(10, 1), 10); d12 = parseInt(IdNumber.substr(11, 1), 10); d13 = parseInt(IdNumber.substr(12, 1), 10); evnum1 = (d2 * 2); evnum2 = (d4 * 2); evnum3 = (d6 * 2); evnum4 = (d8 * 2); evnum5 = (d10 * 2); evnum6 = (d12 * 2); evsum = (CalcSumOfString(evnum1.toString())) + (CalcSumOfString(evnum2.toString())) + (CalcSumOfString(evnum3.toString())) + (CalcSumOfString(evnum4.toString())) + (CalcSumOfString(evnum5.toString())) + (CalcSumOfString(evnum6.toString())); odsum = d1 + d3 + d5 + d7 + d9 + d11; if (((evsum + odsum) % 10) == 0) checkDigit = 0; else checkDigit = 10 - ((evsum + odsum) % 10); if (checkDigit != d13) return false; else return true; } else { return false; } } // [South African Specific] // An enigmatic quest indeed, with no definitive standards published by any SA government department known. The following blogs show the basics for validating SA Identity numbers: // http://www.vanjaarsveld.com/default.aspx // http://codingsanity.blogspot.com/2004/10/id-validation.html // http://www.powertrip.co.za/blog/archives/000007.html // http://www.powertrip.co.za/blog/archives/000135.html // Here's my interpretation and short code-excerpt for validating 'digit-13', the so-called 'check' digit: // Format: // {YYMMDD}{G}{SSS}{C}{A}{Z} // YYMMDD : Date of birth. // G : Gender. 0-4 Female; 5-9 Male. // SSS : Sequence No. for DOB/G combination. // C : Citizenship. 0 SA; 1 Other. // A : Usually 8, or 9 [can be other values] // Z : Control digit calculated in the following section: // Formula to calculate the check digit for a 13 digit identity number: // According to the provisions of the Identification Amendment Act, 2000 (Act No. 28 of 2000, // which was promulgated on 13 October 2000) all forms of identity documents other than the // green bar-coded identity document are invalid. [my observation: the following algorithm appears to work for the older 'blue'-book id numbers as well]. In accordance with the legislation, // the control figure which is the 13th digit of all identity numbers which have 08 and 09 is // calculated as follows using ID Number 800101 5009 087 as an example: // Add all the digits in the odd positions (excluding last digit). // 8 + 0 + 0 + 5 + 0 + 0 = 13...................[1] // Move the even positions into a field and multiply the number by 2. // 011098 x 2 = 22196 // Add the digits of the result in b). // 2 + 2 + 1 + 9 + 6 = 20.........................[2] // Add the answer in [2] to the answer in [1]. // 13 + 20 = 33 // Subtract the second digit (i.e. 3) from 10. The number must tally with the last number in the ID Number. If the result is 2 digits, the last digit is used to compare against the last number in the ID Number. If the answer differs, the ID number is invalid. // Here's the digit-13 check C# code [Check below for a validator using a working version of this code]: // // This method assumes that the 13-digit id number has // // valid digits in position 0 through 12. // // Stored in a property 'ParseIdString'. // // Returns: the valid digit between 0 and 9, or // // -1 if the method fails. // private int GetControlDigit() // { // int d = -1; // try // { // int a = 0; // for(int i = 0; i < 6; i++) // { // a += int.Parse(this.ParsedIdString[2*i].ToString()); // } // int b = 0; // for(int i = 0; i < 6; i++) // { // b = b*10 + int.Parse(this.ParsedIdString[2*i+1].ToString()); // } // b *= 2; // int c = 0; // do // { // c += b % 10; // b = b / 10; // } // while(b > 0); // c += a; // d = 10 - (c % 10); // if(d == 10) d = 0; // } // catch // { // } // return d; // }