Skip to content
This repository has been archived by the owner on Dec 11, 2020. It is now read-only.

Generate valid individual identification numbers kk_KZ #1161

Merged
merged 10 commits into from
Mar 15, 2017
101 changes: 95 additions & 6 deletions src/Faker/Provider/kk_KZ/Person.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,52 @@

namespace Faker\Provider\kk_KZ;

use \Faker\Provider\DateTime;

class Person extends \Faker\Provider\Person
{

const GENDER_MALE = 0;
const GENDER_FEMALE = 1;

const CENTURY_19TH = 0;
const CENTURY_20TH = 1;
const CENTURY_21ST = 2;

const MALE_CENTURY_19TH = 1;
const MALE_CENTURY_20TH = 3;
const MALE_CENTURY_21ST = 5;

const FEMALE_CENTURY_19TH = 2;
const FEMALE_CENTURY_20TH = 4;
const FEMALE_CENTURY_21ST = 6;

/**
* @var array
*/
public static $firstSequenceBitWeights = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11);

/**
* @var array
*/
public static $secondSequenceBitWeights = array(3, 4, 5, 6, 7, 8, 9, 10, 11, 1, 2);

/**
* @var array
*/
public static $genderCenturyMap = array(
self::GENDER_MALE => array(
self::CENTURY_19TH => self::MALE_CENTURY_19TH,
self::CENTURY_20TH => self::MALE_CENTURY_20TH,
self::CENTURY_21ST => self::MALE_CENTURY_21ST,
),
self::GENDER_FEMALE => array(
self::CENTURY_19TH => self::FEMALE_CENTURY_19TH,
self::CENTURY_20TH => self::FEMALE_CENTURY_20TH,
self::CENTURY_21ST => self::FEMALE_CENTURY_21ST,
),
);

/**
* @see https://ru.wikipedia.org/wiki/%D0%9A%D0%B0%D0%B7%D0%B0%D1%85%D1%81%D0%BA%D0%B0%D1%8F_%D1%84%D0%B0%D0%BC%D0%B8%D0%BB%D0%B8%D1%8F
*
Expand Down Expand Up @@ -133,23 +176,69 @@ class Person extends \Faker\Provider\Person
'Ісмет',
);

/**
* @param integer $year
*
* @return integer
Copy link
Contributor

@ankitpokhrel ankitpokhrel Mar 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@return integer|null

*/
private static function getCenturyByYear($year)
{
if ($year >= 1800 && $year <= 1899) {
return self::CENTURY_19TH;
} elseif ($year >= 1900 && $year <= 1999) {
return self::CENTURY_20TH;
} elseif ($year >= 2000 && $year <= DateTime::year()) {
return self::CENTURY_21ST;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can flip conditions and remove &&

if ($year >= 2000 && $year <= DateTime::year()) {
    return self::CENTURY_21ST;
} elseif ($year >= 1900) {
    return self::CENTURY_20TH;
} elseif ($year >= 1800) {
    return self::CENTURY_19TH;
}

}

/**
* National Individual Identification Numbers
*
* @link http://egov.kz/wps/portal/Content?contentPath=%2Fegovcontent%2Fcitizen_migration%2Fpassport_id_card%2Farticle%2Fiin_info&lang=en
* @link https://ru.wikipedia.org/wiki/%D0%98%D0%BD%D0%B4%D0%B8%D0%B2%D0%B8%D0%B4%D1%83%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9_%D0%B8%D0%B4%D0%B5%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D0%BE%D0%BD%D0%BD%D1%8B%D0%B9_%D0%BD%D0%BE%D0%BC%D0%B5%D1%80
*
* @param \DateTime $birthDate
* @param integer $gender
*
* @return string 12 digits, like 780322300455
*/
public static function individualIdentificationNumber(\DateTime $birthDate = null)
public static function individualIdentificationNumber(\DateTime $birthDate = null, $gender = self::GENDER_MALE)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't tested, but looks like your methods can be refactored like this:

public static function individualIdentificationNumber(\DateTime $birthDate = null, $gender = self::GENDER_MALE)
{
    if (!$birthDate) {
        $birthDate = DateTime::dateTimeBetween();
    }

    $population = mt_rand(1000, 2000);
    $century    = self::getCenturyByYear((int) $birthDate->format('Y'));
    
    $iin  = $birthDate->format('ymd');
    $iin .= (string) self::$genderCenturyMap[$gender][$century];
    $iin .= (string) $population;

    return  $iin . (string) self::checkSum($iin);
}

/**
 * @param string $iinValue
 *
 * @return integer
 */
public static function checkSum($iinValue)
{
    $controlDigit = self::getControlDigit($iinValue, self::$firstSequenceBitWeights);

    if ($controlDigit === 10) {
        return self::getControlDigit($iinValue, self::$secondSequenceBitWeights);
    }

    return $controlDigit;
}

/**
 * @param string $iinValue
 * @param array $sequence
 *
 * @return integer
 */
protected static function getControlDigit($iinValue, $sequence) 
{
    $sum = 0;

    for ($i = 0; $i <= 10; $i++) {
        $sum += (int) $iinValue[$i] * $sequence[$i];
    }

    return $sum % 11;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok looks fine

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

{
if (!$birthDate) {
$birthDate = \Faker\Provider\DateTime::dateTimeBetween();
$birthDate = DateTime::dateTimeBetween();
}

$dateAsString = $birthDate->format('ymd');
$genderAndCenturyId = (string) static::numberBetween(1, 6);
$randomDigits = (string) static::numerify('#####');
$dateAsString = $birthDate->format('ymd');
$totalPopulation = rand(1000, 2000);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never, ever use rand in Faker. See explanation in https://github.com/fzaninotto/Faker/blob/master/CONTRIBUTING.md

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

$century = self::getCenturyByYear((int)$birthDate->format('Y'));
$iin = $dateAsString;
$sum = 0;
$calculatedResult = array();

$iin .= (string)self::$genderCenturyMap[$gender][$century];
$iin .= (string)$totalPopulation;

for ($i = 0; $i <= 10; $i++) {
$calculatedResult[$i] = (int)substr($iin, $i, 1);
$sum += $calculatedResult[$i] * self::$firstSequenceBitWeights[$i];
}

$controlDigit = $sum % 11;
$calculatedResult[11] = $controlDigit;

if ($controlDigit === 10) {
$sum = 0;

for ($i = 0; $i <= 10; $i++) {
$calculatedResult[$i] = (int)substr($iin, $i, 1);
$sum += $calculatedResult[$i] * self::$secondSequenceBitWeights[$i];
}

$controlDigit = $sum % 11;
$calculatedResult[11] = $controlDigit;
}

return $dateAsString . $genderAndCenturyId . $randomDigits;
return implode('', $calculatedResult);
}
}
28 changes: 22 additions & 6 deletions test/Faker/Provider/kk_KZ/PersonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
namespace Faker\Test\Provider\kk_KZ;

use Faker\Generator;
use Faker\Provider\DateTime;
use Faker\Provider\kk_KZ\Person;

class PersonTest extends \PHPUnit_Framework_TestCase
Expand All @@ -19,13 +20,28 @@ public function setUp()

public function testIndividualIdentificationNumberIsValid()
{
$birthDate = new \DateTime('now');
$birthDate = DateTime::dateTimeBetween('-30 years', '-10 years');
$individualIdentificationNumber = $this->faker->individualIdentificationNumber($birthDate);
$birthDateAsString = $birthDate->format('ymd');
$sum = 0;

$this->assertRegExp(
"/^(" . $birthDateAsString . ")([1-6]{1})(\\d{5})$/",
$individualIdentificationNumber
);
for ($i = 0; $i <= 10; $i++) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't duplicate the checksum code here. Instead, refactor it into a utility function, and use it both in the generator and in the test.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

$calculatedResult[$i] = (int)substr($individualIdentificationNumber, $i, 1);
$sum += $calculatedResult[$i] * Person::$firstSequenceBitWeights[$i];
}

$controlDigit = $sum % 11;

if ($controlDigit === 10) {
$sum = 0;

for ($i = 0; $i <= 10; $i++) {
$calculatedResult[$i] = (int)substr($individualIdentificationNumber, $i, 1);
$sum += $calculatedResult[$i] * Person::$secondSequenceBitWeights[$i];
}

$controlDigit = $sum % 11;
}

$this->assertTrue($controlDigit === (int)substr($individualIdentificationNumber, 11, 1));
}
}