Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save martindilling/96b9511a66d31a1e8abdfaf29080fed7 to your computer and use it in GitHub Desktop.
Save martindilling/96b9511a66d31a1e8abdfaf29080fed7 to your computer and use it in GitHub Desktop.
Calculate the full birthdate given a CPR number.
<?php
declare(strict_types=1);
/**
* Calculate the full birthdate given a CPR number.
* The logic is described in this document.
*
* @see https://cpr.dk/media/12066/personnummeret-i-cpr.pdf
* @param string $cpr
* @return string|null format:Y-m-d
*/
function cprToBirthdate(string $cpr): ?string
{
if (!is_numeric($cpr) || !$cpr || strlen($cpr) !== 10) {
return null;
}
$day = $cpr[0] . $cpr[1];
$month = $cpr[2] . $cpr[3];
$year = $cpr[4] . $cpr[5];
$seventh = (int) $cpr[6];
switch ($seventh) {
case 0;
case 1;
case 2;
case 3;
$century = '19';
break;
case 4;
case 9:
$century = (int) $year <= 36
? '20'
: '19';
break;
case 5;
case 6;
case 7;
case 8;
$century = (int) $year <= 57
? '20'
: '18';
break;
default:
return null;
}
$fullYear = $century . $year;
if (!checkdate((int) $month, (int) $day, (int) $fullYear)) {
return null;
}
return "{$fullYear}-{$month}-{$day}";
}
<?php
declare(strict_types=1);
require_once '../functions.php';
use PHPUnit\Framework\TestCase;
final class CprToBirthdateTest extends TestCase
{
/**
* @covers ::cprToBirthdate
* @dataProvider casesProvider
*/
public function testCases(string $cpr, ?string $birthdate): void
{
self::assertSame($birthdate, cprToBirthdate($cpr));
}
public function casesProvider(): array
{
return [
// Test edges of all the defined combinations of
// the 5th, 6th and 7th digit.
// 0
['2412000000', '1900-12-24'],
['2412990000', '1999-12-24'],
// 1
['2412001000', '1900-12-24'],
['2412991000', '1999-12-24'],
// 2
['2412002000', '1900-12-24'],
['2412992000', '1999-12-24'],
// 3
['2412003000', '1900-12-24'],
['2412993000', '1999-12-24'],
// 4
['2412004000', '2000-12-24'],
['2412364000', '2036-12-24'],
['2412374000', '1937-12-24'],
['2412994000', '1999-12-24'],
// 5
['2412005000', '2000-12-24'],
['2412575000', '2057-12-24'],
['2412585000', '1858-12-24'],
['2412995000', '1899-12-24'],
// 6
['2412006000', '2000-12-24'],
['2412576000', '2057-12-24'],
['2412586000', '1858-12-24'],
['2412996000', '1899-12-24'],
// 7
['2412007000', '2000-12-24'],
['2412577000', '2057-12-24'],
['2412587000', '1858-12-24'],
['2412997000', '1899-12-24'],
// 8
['2412008000', '2000-12-24'],
['2412578000', '2057-12-24'],
['2412588000', '1858-12-24'],
['2412998000', '1899-12-24'],
// 9
['2412009000', '2000-12-24'],
['2412369000', '2036-12-24'],
['2412379000', '1937-12-24'],
['2412999000', '1999-12-24'],
// Make sure all parts are padded with zero
['0101010000', '1901-01-01'],
// Invalid cases
['', null],
['0000000000', null],
'Invalid length' => ['010101000', null],
'Must be only digits' => ['0101a10000', null],
'Invalid day' => ['3201010000', null],
'Invalid month' => ['0113010000', null],
];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment