'South Africa ID number validation

I've researched this but none of the code I use seems to work. South African ID numbers contain the date of birth and gender. All I want is it to pull in that information and verify it when their ID number is entered into an input field, in angular

I have tried using the javascript code that I currently have just modified it to be in typescript. Im not getting any errors but it's not validating at all.

ts


 this.registerForm = this.formBuilder.group({
  confirm: ['', [Validators.required, Validators.email]],
};
      validateRSAidnumber(idnumber) {
        console.log('idnumber', idnumber);
    
        let invalid = 0;
    
        // check that value submitted is a number
        if (isNaN(idnumber)) {
          invalid++;
        }
    
        // check length of 13 digits
        if (idnumber.length !== 13) {
          invalid++;
        }
    
        // check that YYMMDD group is a valid date
        const yy = idnumber.substring(0, 2);
        const mm = idnumber.substring(2, 4);
        const dd = idnumber.substring(4, 6);
    
        const dob = new Date(yy, (mm - 1), dd);
    
        // check values - add one to month because Date() uses 0-11 for months
        if (!(((dob.getFullYear() + '').substring(2, 4) === yy) && (dob.getMonth() === mm - 1) && (dob.getDate() === dd))) {
          invalid++;
        }
    
        // evaluate GSSS group for gender and sequence 
        const gender = parseInt(idnumber.substring(6, 10), 10) > 5000 ? 'M' : 'F';
    
        // ensure third to last digit is a 1 or a 0
        if (idnumber.substring(10, 11) > 1) {
          invalid++;
        }
    
        // ensure second to last digit is a 8 or a 9
        if (idnumber.substring(11, 12) < 8) {
          invalid++;
        }
    
        // calculate check bit (Z) using the Luhn algorithm
        let ncheck = 0;
        let beven = false;
    
        for (let c = idnumber.length - 1; c >= 0; c--) {
          const cdigit = idnumber.charAt(c);
          let ndigit = parseInt(cdigit, 10);
    
          if (beven) {
            if ((ndigit *= 2) > 9) ndigit -= 9;
          }
    
          ncheck += ndigit;
          beven = !beven;
        }
    
        if ((ncheck % 10) !== 0) {
          invalid++;
        }
    
        return !invalid;
      }
    
    
      // convenience getter for easy access to form fields
      get f() { return this.registerForm.controls; }
      get isEmailMismatch() { return this.registerForm.getError('emailMismatch'); }
    
      onSubmit() {
        console.log('buttoj');
        this.submitted = true;
   
        this.userService.user.user.email = this.email;
        this.userService.user.user.first_name = this.firstName;
        this.userService.user.user.last_name = this.lastName;
        this.userService.user.user.id_number = this.idNumber;
        this.userService.user.user.password = this.password;
        this.userService.user.user.phone = '0' + this.contactNumber.toString();
        this.userService.user.user.id_number = this.idNumber.toString();
        this.registerUser();
        this.validateRSAidnumber(this.idNumber);

}


Solution 1:[1]

Information stored in the South African ID number(e.g 8801235111088):

  • Digits 1 and 2 = year of birth (e.g 1988)
  • Digits 3 and 4 = month of birth (e.g 01/January)
  • Digits 5 and 6 = day of birth (e.g 23rd day)
  • Digits 7 to 10 are used to define gender (Female = 0 to 4999; Male = 5000 to 9999) (e.g 5111 = Male)
  • Digit 11 is used to classify your citizenship (SA citizen = 0; Non-SA citizen = 1)
  • Digit 12 was used until the 1980's to classify race
  • Digit 13 The last is used as a checksum to determine if the ID number is valid

Here is Python script to extract the info from any South African ID number:

Id = str(input("Please enter your South African Identity Number(ID): "))
#Id = "8801235111088"

#Date of birth
year = int(Id[0:2])
month = Id[2:4]
day = Id[4:6]

if year >= 50:
    print("Date of birth is: ", day, month, "19" + str(year) )
if year < 50:
    print("Date of birth is: " , day, month, "20" + str(year))

#gender
gender = int(Id[6:10])
if 0000 <= gender <= 4999:
    print("Gender is : female")
if 5000 <= gender <= 9999:
    print("Gender is: male")


#Citizen status
citizen = int(Id[10])
if citizen == 0:
    print("You are a South African citizen")
if citizen == 1:
    print("You are not a South African citizen")

Solution 2:[2]

I use an id validation service in my angular form validation. You could use it anywhere though. It has been working well in my application for some time now.

idValidation.service.ts

export class IdValidationService {
  constructor() {}

  // going to accept ID as string to account for ID starting with 0
  public isValid(id: string): boolean {
    if (this.tryParseInt(id) === null) {
      return false;
    }

    const idArray = this.toNumberArray(id);
    if (idArray.length !== 13) {
      return false;
    }

    if (!this.isValidDOB(id)) {
      return false;
    }

    return this.validateId(idArray);
  }

  private isValidDOB(id: string): boolean {
    const month = this.tryParseInt(id.substring(2, 4));
    const day = this.tryParseInt(id.substring(4, 6));

    // Validate month and day
    if (
      month < 1 ||
      month > 12 ||
      month == null ||
      day < 1 ||
      day > 31 ||
      day == null
    ) {
      return false;
    }

    //  Apr, Jun, Sep, Nov must not have more than 30 days
    if (
      (month === 4 || month === 6 || month === 9 || month === 11) &&
      day > 30
    ) {
      return false;
    }

    // Feb must not have more than 29 days
    if (month === 2 && day > 29) {
      return false;
    }

    // If Feb and day is 29, make sure is leap year
    if (month === 2 && day === 29) {
      let year = this.tryParseInt(id.substring(0, 2));
      const currentYearTwoDigit = new Date().getFullYear() % 100;
      year += year > currentYearTwoDigit ? 1900 : 2000;

      // Check if is leap year
      if (year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0)) {
        return true;
      }

      return false;
    }

    return true;
  }

  private toNumberArray(id: string): number[] {
    return id.split('').map((num) => Number(num));
  }

  private tryParseInt(value: string, defaultValue = null): number {
    const parsedValue = parseInt(value, 10);

    if (isNaN(parsedValue)) {
      return defaultValue;
    }
    return parsedValue;
  }

  private validateId(idArray: number[]): boolean {
    // get check digit (last digit of id)
    const checkDigit = idArray[12];
    // get list of odd values except check digit
    const oddValueArray = this.getOddValueArray(idArray);
    // add odd values together
    const oddValueSum = this.getSumOfNumberArray(oddValueArray);
    // get list of even values
    const evenValueArray = this.getEvenValueArray(idArray);
    // concat even values into one number
    let concatedEvenValues = this.concatNumberArray(evenValueArray);
    // multiple concated even numbers value by 2
    concatedEvenValues = concatedEvenValues * 2;
    // get list of values in new concated even number's value
    const concatedEvenValuesArray = this.toNumberArray(
      String(concatedEvenValues)
    );
    // add list of concated even number values together
    const sumOfConcatedEvenValues = this.getSumOfNumberArray(
      concatedEvenValuesArray
    );
    // add odd value sum and concated even number sum together
    const sumofOddAndEven = oddValueSum + sumOfConcatedEvenValues;
    // convert the above sum into a list of values
    const sumOfOddAndEvenArray = this.toNumberArray(String(sumofOddAndEven));
    // get last digit of above value
    const lastDigit = sumOfOddAndEvenArray[sumOfOddAndEvenArray.length - 1];
    // get verifyCheckDigit which is 10 minus the above last digit
    // (I am using %10 to ensure that 10-0 = 0, because if 10-0 = 10 compare digits will return false)
    let verifyCheckDigit = (10 - lastDigit) % 10;
    // if verifyCheckDigit has two digits, get the last digit
    if (verifyCheckDigit > 9) {
      verifyCheckDigit = this.toNumberArray(String(verifyCheckDigit)).pop();
    }
    // compare check digits and return true or false
    return this.compareCheckDigits(checkDigit, verifyCheckDigit);
  }

  private getSumOfNumberArray(numberArray: number[]): number {
    let sum = 0;
    for (let i = 0; i < numberArray.length; i++) {
      sum = sum + numberArray[i];
    }
    return sum;
  }

  private getOddValueArray(idArray: number[]): number[] {
    const oddArray = [];
    oddArray.push(idArray[0]);
    oddArray.push(idArray[2]);
    oddArray.push(idArray[4]);
    oddArray.push(idArray[6]);
    oddArray.push(idArray[8]);
    oddArray.push(idArray[10]);
    return oddArray;
  }

  private getEvenValueArray(idArray: number[]): number[] {
    const evenArray = [];
    evenArray.push(idArray[1]);
    evenArray.push(idArray[3]);
    evenArray.push(idArray[5]);
    evenArray.push(idArray[7]);
    evenArray.push(idArray[9]);
    evenArray.push(idArray[11]);
    return evenArray;
  }

  private concatNumberArray(numberArray: number[]): number {
    return Number(numberArray.join(''));
  }

  private compareCheckDigits(
    checkDigit: number,
    verifyCheckDigit: number
  ): boolean {
    if (verifyCheckDigit === checkDigit) {
      return true;
    }
    return false;
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Hedgybeats