PersonalId   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 101
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 16
lcom 1
cbo 5
dl 0
loc 101
ccs 20
cts 20
cp 1
rs 10
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 43 8
A getBirthDate() 0 4 1
A getSex() 0 4 2
A getBirthCounty() 0 14 4
A validateCheckDigit() 0 4 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace byrokrat\id;
6
7
use byrokrat\id\Helper\BasicIdTrait;
8
use byrokrat\id\Helper\DateTimeCreator;
9
use byrokrat\id\Helper\Modulo10;
10
use byrokrat\id\Helper\NumberParser;
11
use byrokrat\id\Exception\InvalidDateStructureException;
12
use byrokrat\id\Exception\UnableToCreateIdException;
13
14
class PersonalId implements IdInterface
15
{
16
    use BasicIdTrait;
17
18
    /**
19
     * Regular expression describing id structure
20
     */
21
    protected const PATTERN = '/^((?:\d\d)?)(\d{6})([-+]?)(\d{3})(\d)$/';
22
23
    /**
24
     * @var \DateTimeImmutable
25
     */
26
    private $dateOfBirth;
27
28
    /**
29
     * Create swedish personal identity number
30
     *
31
     * Format is YYYYMMDD(+-)NNNC or YYMMDD(+-)NNNC where parenthesis represents
32
     * an optional one char delimiter, N represents the individual number and C
33
     * the check digit. If year is set using two digits century is calculated
34
     * based on delimiter (+ signals more than a hundred years old). If year is
35
     * set using four digits delimiter is calculated based on century.
36
     *
37
     * @param string $raw The raw id to parse
38
     * @param \DateTimeInterface $atDate Optional date when parsing takes place, defaults to today
39
     * @throws UnableToCreateIdException On failure to create id
40
     */
41
    public function __construct(string $raw, \DateTimeInterface $atDate = null)
42
    {
43
        $atDate = $atDate ?: new \DateTime();
44
45
        list($century, $this->serialPre, $delimiter, $this->serialPost, $this->checkDigit)
46
            = NumberParser::parse(self::PATTERN, $raw);
47
48
        $this->delimiter = $delimiter ?: '-';
49
50
        if ($century) {
51
            // Date of birth is fully defined with 8 digits
52
            $dateOfBirth = DateTimeCreator::createFromFormat('Ymd', $century . $this->serialPre);
53
54
            // Calculate the first day the delimiter should be changed to '+'
55
            $firstDayCountingAsHundred = DateTimeCreator::createFromFormat('Ymd', $dateOfBirth->format('Y') . '0101');
56
            $firstDayCountingAsHundred->modify('+100 year');
57
58
            // Set delimiter based on current date
59
            $this->delimiter = $atDate < $firstDayCountingAsHundred ? '-' : '+';
60
        } else {
61
            // No century defined for date of birth, guess..
62
            $dateOfBirth = DateTimeCreator::createFromFormat('ymd', $this->serialPre);
63
64 97
            // If date of birth is in the future century is wrong
65
            if ($dateOfBirth > $atDate) {
66 74
                $dateOfBirth->modify('-100 year');
67 97
            }
68
69 74
            // If delimiter equals '+' date should be at least a hundred years ago
70
            if ($this->getDelimiter() == '+') {
71 74
                $dateOfBirth->modify('-100 year');
72
            }
73 16
        }
74 16
75 16
        // Validate that date is logically valid
76 16
        if ($dateOfBirth->format('ymd') != $this->serialPre) {
77
            throw new InvalidDateStructureException("Invalid date in {$this->getId()}");
78
        }
79 59
80
        $this->dateOfBirth = \DateTimeImmutable::createFromMutable($dateOfBirth);
81
82 59
        $this->validateCheckDigit();
83 2
    }
84
85
    public function getBirthDate(): \DateTimeImmutable
86
    {
87 59
        return $this->dateOfBirth;
88 21
    }
89
90
    public function getSex(): string
91
    {
92
        return (intval($this->getSerialPostDelimiter()[2]) % 2 == 0) ? Sexes::SEX_FEMALE : Sexes::SEX_MALE;
93 74
    }
94 1
95
    public function getBirthCounty(): string
96
    {
97 73
        if ($this->getBirthDate() < DateTimeCreator::createFromFormat('Ymd', '19900101')) {
98 37
            $countyNr = (int)substr($this->getSerialPostDelimiter(), 0, 2);
99
100
            foreach (Counties::COUNTY_NUMBER_MAP as $limit => $identifier) {
101
                if ($countyNr <= $limit) {
102
                    return $identifier;
103
                }
104
            }
105 21
        }
106
107 21
        return Counties::COUNTY_UNDEFINED;
108
    }
109
110
    protected function validateCheckDigit(): void
111
    {
112
        Modulo10::validateCheckDigit($this);
113
    }
114
}
115