Skip to content

Commit

Permalink
Refactor code to use intergers and strings
Browse files Browse the repository at this point in the history
  • Loading branch information
claudemyburgh committed Jun 21, 2024
1 parent d4d91ac commit 9a5e813
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 53 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"require": {
"php": "^8.1|^8.2|^8.3",
"designbycode/luhn-algorithm": "^1.0",
"nesbot/carbon": "^3.5"
"nesbot/carbon": "^3.6",
"symfony/translation": "^7.1"
},
"require-dev": {
"pestphp/pest": "^2.20",
Expand Down
94 changes: 59 additions & 35 deletions src/SouthAfricanIdValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,92 +2,92 @@

namespace Designbycode\SouthAfricanIdValidator;

use Carbon\Carbon;
use Designbycode\LuhnAlgorithm\LuhnAlgorithm;

class SouthAfricanIdValidator
{

private const GENDER_MALE_MIN = 5000;
private const GENDER_MALE_MAX = 9999;
private const GENDER_FEMALE_MIN = 0;
private const GENDER_FEMALE_MAX = 4999;

/**
* @param string $idNumber
* @param mixed $idNumber
* @return bool
* Check if id is valid
*/
public function isValid(string $idNumber): bool
public function isValid(mixed $idNumber): bool
{
return ! (! $this->isLength13($idNumber) || ! $this->isNumber($idNumber) || ! $this->isValidLuhn($idNumber));
$idNumber = $this->trimWhiteSpaces($idNumber);
return ! (! $this->isLength13($idNumber) || ! $this->isNumber($idNumber) || ! $this->passesLuhnCheck($idNumber));
}

// Method to validate if the ID number has a length of 13 digits
public function isLength13($idNumber): bool
{
return strlen((string) $idNumber) === 13;
return strlen((string) $this->trimWhiteSpaces($idNumber)) === 13;
}

public function isNumber($idNumber): bool
{
return is_numeric($idNumber);
return is_numeric($this->trimWhiteSpaces($idNumber));
}

// Method to validate the ID number using the Luhn Algorithm
public function isValidLuhn(string $idNumber): bool
public function passesLuhnCheck(string $idNumber): bool
{
return (new LuhnAlgorithm)->isValid($idNumber);
return (new LuhnAlgorithm)->isValid($this->trimWhiteSpaces($idNumber));
}

// Method to determine if the ID number is for a male
public function isMale($idNumber): bool
private function extractGenderDigits(string $idNumber): int
{
// Extract the gender digits (4 digits after the first 6 digits)
$genderDigits = substr($idNumber, 6, 4);
$genderNumber = (int) $genderDigits;
return (int) substr($idNumber, 6, 4);
}

// Determine if the number falls in the male range
return ($genderNumber >= 5000) && ($genderNumber <= 9999);
// Method to determine if the ID number is for a male
public function isMale(string $idNumber): bool
{
$genderDigits = $this->extractGenderDigits($idNumber);
return $genderDigits >= self::GENDER_MALE_MIN && $genderDigits <= self::GENDER_MALE_MAX;
}

// Method to determine if the ID number is for a female
public function isFemale($idNumber): bool
public function isFemale(string $idNumber): bool
{
// Extract the gender digits (4 digits after the first 6 digits)
$genderDigits = substr($idNumber, 6, 4);
$genderNumber = (int) $genderDigits;

// Determine if the number falls in the female range
return $genderNumber >= 0 && $genderNumber <= 4999;
$genderDigits = $this->extractGenderDigits($idNumber);
return $genderDigits >= self::GENDER_FEMALE_MIN && $genderDigits <= self::GENDER_FEMALE_MAX;
}

// Method to determine if the person is a South African citizen
public function isSACitizen($idNumber): bool
{
$idNumber = $this->trimWhiteSpaces($idNumber);
// The 11th digit (index 10) indicates citizenship status
return $idNumber[10] == '0';
}

// Method to determine if the person is a permanent resident
public function isPermanentResident($idNumber): bool
{
$idNumber = $this->trimWhiteSpaces($idNumber);
// The 11th digit (index 10) indicates citizenship status
return $idNumber[10] == '1';
}

// Method to parse the ID number
public function parse($idNumber): array
{
// Check if the ID number is valid
if (! $this->isValid($idNumber)) {
return ['error' => 'Invalid ID number'];
$idNumber = $this->trimWhiteSpaces($idNumber);
if (!$this->isValid($idNumber)) {
throw new \InvalidArgumentException('Invalid ID number');
}

// Extract the birthdate
$year = substr($idNumber, 0, 2);
$month = substr($idNumber, 2, 2);
$day = substr($idNumber, 4, 2);

// Determine century
$currentYear = (int) date('Y');
$year = (int) $year + ($year > substr($currentYear, 2, 2) ? 1900 : 2000);
$birthDate = $this->extractBirthDate($idNumber);

$birthDate = sprintf('%04d/%02d/%02d', $year, $month, $day);
$age = $currentYear - $year;
// Determine age
$age = $birthDate->diffInYears(Carbon::now());

// Determine gender
$gender = $this->isMale($idNumber) ? 'Male' : 'Female';
Expand All @@ -97,10 +97,34 @@ public function parse($idNumber): array

return [
'valid' => $this->isValid($idNumber),
'birthday' => $birthDate,
'birthday' => [
'default' => $birthDate,
'iso' => $birthDate->format('Y-m-d'),
'american' => $birthDate->format('m/d/Y'),
'european' => $birthDate->format('d/m/Y'),
'long' => $birthDate->format('F j, Y'),
],
'age' => $age,
'gender' => $gender,
'citizenship' => $citizenship,
];
}

private function trimWhiteSpaces(mixed $idNumber): string
{
$idNumber = trim((string) $idNumber); // Remove spaces around the string
return str_replace(' ', '', $idNumber); // Remove spaces within the string
}

private function extractBirthDate(string $idNumber): Carbon
{
$year = (int) substr($idNumber, 0, 2);
$month = (int) substr($idNumber, 2, 2);
$day = (int) substr($idNumber, 4, 2);

$currentYear = (int) date('Y');
$year = $year + ($year > substr($currentYear, 2, 2) ? 1900 : 2000);

return Carbon::createFromDate($year, $month, $day);
}
}
68 changes: 51 additions & 17 deletions tests/ValidatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,82 @@

beforeEach(function () {
$this->validator = new SouthAfricanIdValidator;
$this->id = '7804295117087';
// $this->id = "9202204720082";
});
$this->id = '921223 0051 08 0';

it('can equal id number', function () {
expect($this->id)->toEqual('7804295117087');
});

it('can validate that number has a length of 13', function () {
expect($this->validator->isLength13($this->id))
it('can validate that number has a length of 13', function ($id) {
expect($this->validator->isLength13($id))
->toEqual(13);
});
})->with(['6901240689086', '8907290565082', '7809090453082', 9108200519082, '900601 0051 08 2']);

it('can test that ID is a number', function () {
expect($this->validator->isNumber($this->id))->toBeTrue();
});

it('check if id pass luhna validation', function () {
expect($this->validator->isValidLuhn($this->id))
expect($this->validator->passesLuhnCheck($this->id))
->toBeTrue();
});

it('can validate if is male', function () {
expect($this->validator->isMale($this->id))
->toBeTrue();
->toBeFalse();
});

it('can validate if is female', function () {
expect($this->validator->isFemale($this->id))
->toBeFalse();
->toBeTrue();
});

it('can determine if the person is a South African citizen', function () {
expect($this->validator->isSACitizen($this->id))
it('can determine if the person is a South African citizen', function ($id) {
expect($this->validator->isSACitizen($id))
->toBeTrue();
});
})->with(['690124 0689 08 6', 8907290565082, '7809090453082', '9108200519082', '9006010051082']);

it('can determine if the person is a permanent resident', function () {
expect($this->validator->isPermanentResident($this->id))
it('can determine if the person is a permanent resident', function ($id) {
expect($this->validator->isPermanentResident($id))
->toBeFalse();
});
})->with(['6901240689086', '8907290565082', '7809090453082', 9108200519082, '9006010051082']);

it('can validate ID', function () {
expect($this->validator->isValid($this->id))->toBeTrue();
});


it('can validate list of ID', function ($value) {
expect($this->validator->isValid($this->id))->toBeTrue();
})->with(['690124 0689 08 6', '8907290565082', '7809090453082', 9108200519082, '9006010051082']);



it('parses a valid ID number', function () {
$result = $this->validator->parse($this->id);
expect($result['valid'])->toBeTrue()
->and($result['birthday']['default'])->toBeInstanceOf(Carbon\Carbon::class)
->and($result['age'])->toBeGreaterThan(0)
->and($result['gender'])->toBeString()
->and($result['citizenship'])->toBeString();
});


it('should equal date of ISO from', function() {
$result = $this->validator->parse($this->id);
expect($result['birthday']['iso'])->toEqual('1992-12-23');
});

it('should equal date of american from', function() {
$result = $this->validator->parse($this->id);
expect($result['birthday']['american'])->toEqual('12/23/1992');
});

it('should equal date of european from', function() {
$result = $this->validator->parse($this->id);
expect($result['birthday']['european'])->toEqual('23/12/1992');
});

it('should equal date of long format from', function() {
$result = $this->validator->parse($this->id);
expect($result['birthday']['long'])->toEqual('December 23, 1992');
});

0 comments on commit 9a5e813

Please sign in to comment.