Tell me how to check for valid South african ID numbers. Are there checksums? It starts with yymmdd so at least you know that 009999 is an invalid start. Write a list of all things that can be considered
Valid South African ID numbers follow a specific format and contain a few checks that can be used to determine their validity. Here is a list of things you can consider when checking the validity of a South African ID number:
-
Length: A valid South African ID number should be 13 digits long.
-
Birthdate: The first six digits should represent the birthdate of the person in the format YYMMDD. This means that an ID number starting with "009999" would be invalid as you mentioned.
-
Gender: The 7th to 9th digits indicate the gender of the person. A value of 0000-4999 represents a female, while 5000-9999 represents a male.
-
Citizenship: The 10th digit represents citizenship. A value of 0 indicates a South African citizen, while 1 indicates a permanent resident.
-
11th and 12th digits: These are sequential numbers for applicants with the same birthdate and gender.
-
Checksum: The 13th digit is a checksum calculated using the Luhn algorithm. To verify the checksum:
a. Double the value of every second digit from the right (i.e., the 2nd, 4th, 6th, 8th, 10th, and 12th digits). b. If doubling any digit results in a two-digit number, add the two digits together to get a single-digit result. c. Add the results from step (b) together with the remaining (non-doubled) digits. d. Calculate the remainder when the sum from step (c) is divided by 10. e. If the remainder is 0, the checksum digit should be 0. Otherwise, the checksum digit should be 10 minus the remainder.
For example, consider the ID number 8001015009087:
- Length: 13 digits
- Birthdate: 1980-01-01
- Gender: Male (5009)
- Citizenship: South African citizen (0)
- 11th and 12th digits: 09
- Checksum: (8+0+0+2+1+2+5+1+0+18+0+16) % 10 = 53 % 10 = 3; Checksum = 10 - 3 = 7; Matches the last digit, so it's valid.
By checking each of these components, you can determine the validity of a South African ID number.
Here is a Python implementation. Write this into a single VBA function with lots of comments:
from enum import Enum, auto
RSA_ID_LENGTH = 13
DATE_OF_BIRTH_FORMAT = "%y%m%d"
GENDER_FEMALE_MIN = 0
GENDER_FEMALE_MAX = 4999
GENDER_MALE_MIN = 5000
GENDER_MALE_MAX = 9999
SA_CITIZEN_DIGIT = "0"
PERMANENT_RESIDENT_DIGIT = "1"
RACE_DIGIT = "8" # used until 1980s
class Gender(Enum):
FEMALE = auto()
MALE = auto()
class Citizenship(Enum):
SA_CITIZEN = auto()
PERMANENT_RESIDENT = auto()
===============================================
import logging
import re
from datetime import datetime
from .constants import (
DATE_OF_BIRTH_FORMAT,
GENDER_FEMALE_MAX,
GENDER_FEMALE_MIN,
PERMANENT_RESIDENT_DIGIT,
RSA_ID_LENGTH,
SA_CITIZEN_DIGIT,
Citizenship,
Gender,
)
from .random import generate
__version__ = "0.0.3"
__all__ = ["Gender", "Citizenship", "IdNumber", "parse", "generate"]
logger = logging.getLogger(__name__)
class IdNumber:
def __init__(self, value: str):
self.value = value
self.error = None
self.date_of_birth = None
self.gender = None
self.citizenship = None
self.parse()
def clean(self):
"""Return the value without any whitespace."""
return re.sub(r"\s", "", self.value or "")
def parse(self):
"""Parse the value and validate against the RSA ID number format."""
self.error = None
self.date_of_birth = None
self.gender = None
self.citizenship = None
value = self.clean()
if not value:
self.error = f"'{value}' is not a valid RSA ID number!"
return
if not value.isdigit():
self.error = f"'{value}' contains non-digit characters!"
return
if len(value) != RSA_ID_LENGTH:
self.error = f"'{value}' is not {RSA_ID_LENGTH} digits!"
return
year = value[0:2]
month = value[2:4]
day = value[4:6]
try:
self.date_of_birth = datetime.strptime(
f"{year}{month}{day}", DATE_OF_BIRTH_FORMAT
)
if self.date_of_birth > datetime.now():
correct_year = self.date_of_birth.year - 100
self.date_of_birth = self.date_of_birth.replace(
year=correct_year
)
except ValueError:
self.error = f"'{value}' contains an invalid date of birth!"
logger.debug(self.error, exc_info=True)
return
gender = int(value[6:10])
if gender >= GENDER_FEMALE_MIN and gender <= GENDER_FEMALE_MAX:
self.gender = Gender.FEMALE
else:
self.gender = Gender.MALE
citizenship = value[10]
if citizenship == SA_CITIZEN_DIGIT:
self.citizenship = Citizenship.SA_CITIZEN
elif citizenship == PERMANENT_RESIDENT_DIGIT:
self.citizenship = Citizenship.PERMANENT_RESIDENT
else:
self.error = f"Invalid citizenship indicator: '{citizenship}'!"
return
digits = [int(d) for d in value]
digits.reverse()
sum = 0
for index, digit in enumerate(digits):
if (index + 1) % 2 == 0:
digit = digit * 2
if digit > 9:
digit = digit - 9
sum = sum + digit
if not sum % 10 == 0:
self.error = f"'{value}' contains an invalid checksum digit!"
return
@property
def valid(self) -> bool:
"""Return True if there is not error, False otherwise."""
return not self.error
def __repr__(self):
return self.clean()
def parse(value: str, raise_exc: bool = True) -> IdNumber:
"""Parse `value` and validate against the RSA ID number format.
Args:
value: ID number string to parse and validate.
Returns:
A new `IdNumber` instance.
Raises:
ValueError: If the ID number is invalid and `raise_exc` is True.
Examples:
>>> import rsaidnumber
>>> value = '1234567890123'
>>> id_number = rsaidnumber.parse(value)
"""
id_number = IdNumber(value)
id_number.parse()
if not id_number.valid and raise_exc:
raise ValueError(id_number.error)
return id_number
Here's a single VBA function implementing the provided Python code. Please note that VBA does not support Enums, so the Gender and Citizenship Enums are replaced with their respective string values:
Function IsValidSouthAfricanID(IDNumber As String) As Boolean
Const ID_LENGTH As Integer = 13
Const GENDER_FEMALE_MIN As Integer = 0
Const GENDER_FEMALE_MAX As Integer = 4999
Const GENDER_MALE_MIN As Integer = 5000
Const GENDER_MALE_MAX As Integer = 9999
Const SA_CITIZEN_DIGIT As String = "0"
Const PERMANENT_RESIDENT_DIGIT As String = "1"
Dim IDValue As String
Dim IDLength As Integer
Dim dob As String
Dim genderValue As Integer
Dim citizenship As String
' Clean and check input
IDValue = Replace(IDNumber, " ", "")
IDLength = Len(IDValue)
If IDLength <> ID_LENGTH Then
IsValidSouthAfricanID = False
Exit Function
End If
' Check date of birth
dob = Left(IDValue, 6)
If Not IsDate(Format(dob, "00\/00\/00")) Then
IsValidSouthAfricanID = False
Exit Function
End If
' Check gender
genderValue = Mid(IDValue, 7, 4)
If genderValue < GENDER_FEMALE_MIN Or genderValue > GENDER_MALE_MAX Then
IsValidSouthAfricanID = False
Exit Function
End If
' Check citizenship
citizenship = Mid(IDValue, 11, 1)
If citizenship <> SA_CITIZEN_DIGIT And citizenship <> PERMANENT_RESIDENT_DIGIT Then
IsValidSouthAfricanID = False
Exit Function
End If
' Check Luhn checksum
Dim i As Integer
Dim currentDigit As Integer
Dim doubledDigit As Integer
Dim total As Integer
total = 0
For i = ID_LENGTH To 1 Step -1
currentDigit = Mid(IDValue, i, 1)
If (ID_LENGTH - i) Mod 2 = 1 Then
doubledDigit = currentDigit * 2
If doubledDigit > 9 Then
doubledDigit = doubledDigit - 9
End If
total = total + doubledDigit
Else
total = total + currentDigit
End If
Next i
IsValidSouthAfricanID = (total Mod 10 = 0)
End Function
This VBA function takes an ID number as input and returns True if the ID number is valid, otherwise it returns False. It checks for length, date of birth, gender, citizenship, and Luhn checksum.
is this a valid ID? 8905141372087
To check if the given South African ID number (8905141372087) is valid, let's go through the checks:
- Length: 13 digits
- Birthdate: 1989-05-14
- Gender: Female (1372 falls between 0000 and 4999)
- Citizenship: South African citizen (0)
- 11th and 12th digits: 20
- Checksum: a. Doubled digits: 12=2, 02=0, 72=14 (1+4=5), 12=2, 92=18 (1+8=9), 82=16 (1+6=7) b. Sum of the results with the non-doubled digits: 2+8+0+5+1+4+2+1+9+3+7 = 42 c. Calculate the remainder when the sum is divided by 10: 42 % 10 = 2 d. Checksum digit: 10 - 2 = 8
The checksum digit (8) matches the last digit of the given ID number (8905141372087). Therefore, this ID number is valid.
I basically copied the Python content from
https://github.com/teamgeek-io/rsa-id-number/blob/develop/src/rsaidnumber/constants.py
and
https://github.com/teamgeek-io/rsa-id-number/blob/develop/src/rsaidnumber/constants.py
Interesting enough, ChatGPT provided correct code for validating the invalid ID "8905141372087" as invalid, but when trying to validate it itself, it botched the checksum check and suggested the ID is valid.