Skip to content

Commit

Permalink
Create "Hetu" rule
Browse files Browse the repository at this point in the history
This rule validates Finnish personal identity codes.

Co-authored-by: Henrique Moody <[email protected]>
  • Loading branch information
vhuk and henriquemoody committed Mar 15, 2024
1 parent 52b75bc commit a5d042b
Show file tree
Hide file tree
Showing 13 changed files with 235 additions and 0 deletions.
2 changes: 2 additions & 0 deletions docs/08-list-of-rules-by-category.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
- [Cnh](rules/Cnh.md)
- [Cnpj](rules/Cnpj.md)
- [Cpf](rules/Cpf.md)
- [Hetu](rules/Hetu.md)
- [Imei](rules/Imei.md)
- [Isbn](rules/Isbn.md)
- [Luhn](rules/Luhn.md)
Expand Down Expand Up @@ -344,6 +345,7 @@
- [Graph](rules/Graph.md)
- [GreaterThan](rules/GreaterThan.md)
- [GreaterThanOrEqual](rules/GreaterThanOrEqual.md)
- [Hetu](rules/Hetu.md)
- [HexRgbColor](rules/HexRgbColor.md)
- [Iban](rules/Iban.md)
- [Identical](rules/Identical.md)
Expand Down
1 change: 1 addition & 0 deletions docs/rules/Cnh.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ See also:
- [Bsn](Bsn.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [Imei](Imei.md)
- [NfeAccessKey](NfeAccessKey.md)
- [Nif](Nif.md)
Expand Down
1 change: 1 addition & 0 deletions docs/rules/Cnpj.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ See also:
- [Bsn](Bsn.md)
- [Cnh](Cnh.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [Imei](Imei.md)
- [NfeAccessKey](NfeAccessKey.md)
- [Nif](Nif.md)
Expand Down
1 change: 1 addition & 0 deletions docs/rules/Cpf.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ See also:
- [Bsn](Bsn.md)
- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Hetu](Hetu.md)
- [Imei](Imei.md)
- [NfeAccessKey](NfeAccessKey.md)
- [Nif](Nif.md)
Expand Down
37 changes: 37 additions & 0 deletions docs/rules/Hetu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Hetu

- `Hetu()`

Validates a Finnish personal identity code ([HETU][]).

```php
v::hetu()->validate('010106A9012'); // true
v::hetu()->validate('290199-907A'); // true
v::hetu()->validate('280291+923X'); // true

v::hetu()->validate('010106_9012'); // false
```

The validation is case-sensitive.

## Categorization

- Identifications

## Changelog

| Version | Description |
|--------:|-------------|
| 3.0.0 | Created |

***
See also:

- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Imei](Imei.md)
- [Nif](Nif.md)
- [PortugueseNif](PortugueseNif.md)

[HETU]: https://en.wikipedia.org/wiki/National_identification_number#Finland
1 change: 1 addition & 0 deletions docs/rules/Imei.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ See also:
- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [Isbn](Isbn.md)
- [Luhn](Luhn.md)

Expand Down
1 change: 1 addition & 0 deletions docs/rules/Nif.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ See also:
- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [PortugueseNif](PortugueseNif.md)
1 change: 1 addition & 0 deletions docs/rules/PortugueseNif.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ See also:
- [Cnh](Cnh.md)
- [Cnpj](Cnpj.md)
- [Cpf](Cpf.md)
- [Hetu](Hetu.md)
- [Nif](Nif.md)
2 changes: 2 additions & 0 deletions library/Mixins/ChainedValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ public function graph(string ...$additionalChars): ChainedValidator;

public function greaterThan(mixed $compareTo): ChainedValidator;

public function hetu(): ChainedValidator;

public function hexRgbColor(): ChainedValidator;

public function iban(): ChainedValidator;
Expand Down
2 changes: 2 additions & 0 deletions library/Mixins/StaticValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public static function graph(string ...$additionalChars): ChainedValidator;

public static function greaterThan(mixed $compareTo): ChainedValidator;

public static function hetu(): ChainedValidator;

public static function hexRgbColor(): ChainedValidator;

public static function iban(): ChainedValidator;
Expand Down
58 changes: 58 additions & 0 deletions library/Rules/Hetu.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Rules;

use Respect\Validation\Helpers\CanValidateDateTime;
use Respect\Validation\Message\Template;
use Respect\Validation\Rules\Core\Simple;

use function is_string;
use function preg_match;
use function str_split;

/**
* @see https://en.wikipedia.org/wiki/National_identification_number#Finland
*/
#[Template(
'{{name}} must be a valid Finnish personal identity code',
'{{name}} must not be a valid Finnish personal identity code',
)]
final class Hetu extends Simple
{
use CanValidateDateTime;

public function validate(mixed $input): bool
{
if (!is_string($input)) {
return false;
}

if (!preg_match('/^(\d{2})(\d{2})(\d{2})([+\-A-FU-Y])(\d{3})([0-9A-FHJ-NPR-Y])$/', $input, $matches)) {
return false;
}

[, $day, $month, $year, $centuryMarker, $individualNumber, $controlCharacter] = $matches;

// @phpstan-ignore-next-line
$century = match ($centuryMarker) {
'+' => '18',
'-', 'U', 'V', 'W', 'X', 'Y' => '19',
'A', 'B', 'C', 'D', 'E', 'F' => '20',
};
if (!$this->isDateTime('dmY', $day . $month . $century . $year)) {
return false;
}

$id = $day . $month . $year . $individualNumber;
$validationKeys = str_split('0123456789ABCDEFHJKLMNPRSTUVWXY');

return $validationKeys[$id % 31] === $controlCharacter;
}
}
49 changes: 49 additions & 0 deletions tests/integration/rules/hetu.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
--FILE--
<?php

declare(strict_types=1);

require 'vendor/autoload.php';

use Respect\Validation\Validator as v;

run([
'Default' => [v::hetu(), '010106A901O'],
'Negative' => [v::not(v::hetu()), '010106A9012'],
'With template' => [v::hetu(), '010106A901O', 'That is not a HETU'],
'With name' => [v::hetu()->setName('Hetu'), '010106A901O'],
]);
?>
--EXPECT--
Default
⎺⎺⎺⎺⎺⎺⎺
"010106A901O" must be a valid Finnish personal identity code
- "010106A901O" must be a valid Finnish personal identity code
[
'hetu' => '"010106A901O" must be a valid Finnish personal identity code',
]

Negative
⎺⎺⎺⎺⎺⎺⎺⎺
"010106A9012" must not be a valid Finnish personal identity code
- "010106A9012" must not be a valid Finnish personal identity code
[
'hetu' => '"010106A9012" must not be a valid Finnish personal identity code',
]

With template
⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺⎺
That is not a HETU
- That is not a HETU
[
'hetu' => 'That is not a HETU',
]

With name
⎺⎺⎺⎺⎺⎺⎺⎺⎺
Hetu must be a valid Finnish personal identity code
- Hetu must be a valid Finnish personal identity code
[
'Hetu' => 'Hetu must be a valid Finnish personal identity code',
]

79 changes: 79 additions & 0 deletions tests/unit/Rules/HetuTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

/*
* Copyright (c) Alexandre Gomes Gaigalas <[email protected]>
* SPDX-License-Identifier: MIT
*/

declare(strict_types=1);

namespace Respect\Validation\Rules;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\Attributes\Test;
use Respect\Validation\Test\TestCase;

#[Group('rule')]
#[CoversClass(Hetu::class)]
final class HetuTest extends TestCase
{
#[Test]
#[DataProvider('providerForNonStringTypes')]
public function itShouldAlwaysInvalidateWhenValueIsNotString(mixed $input): void
{
self::assertInvalidInput(new Hetu(), $input);
}

#[Test]
#[DataProvider('providerForInvalidHetu')]
public function itShouldInvalidateInvalidHetu(string $input): void
{
self::assertInvalidInput(new Hetu(), $input);
}

#[Test]
#[DataProvider('providerForValidHetu')]
public function itShouldValidateValidHetu(string $input): void
{
self::assertValidInput(new Hetu(), $input);
}

/** @return array<array{string}> */
public static function providerForValidHetu(): array
{
return [
['010106A9012'],
['290199-907A'],
['010199+9012'],
['280291+913L'],
['280291+923X'],
];
}

/** @return array<array{string}> */
public static function providerForInvalidHetu(): array
{
return [
['010106a9012'],
['010106_9012'],
['010106G9012'],
['010106Z9012'],
['010106A901G'],
['010106A901I'],
['010106A901O'],
['010106A901Q'],
['010106A901Z'],
['010106!9012'],
['010106'],
['01X199+9012'],
['01X199Z9012'],
['01X199T9012'],
['999999A9999'],
['999999A999F'],
['300201A1236'],
['290201A123J'],
];
}
}

0 comments on commit a5d042b

Please sign in to comment.