From b7ee636c6fc96566b55d40bb9186edb513004fbe Mon Sep 17 00:00:00 2001 From: Huanyi Xie <74809554+xiehuanyi@users.noreply.github.com> Date: Tue, 13 Oct 2026 11:34:34 +0300 Subject: [PATCH] Validate Finnish identity code birth dates --- src/validators/i18n/fi.py | 6 +++++- tests/i18n/test_fi.py | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/validators/i18n/fi.py b/src/validators/i18n/fi.py index 12fb281b..0ccda256 100644 --- a/src/validators/i18n/fi.py +++ b/src/validators/i18n/fi.py @@ -1,6 +1,7 @@ """Finland.""" # standard +from datetime import date from functools import lru_cache import re @@ -22,7 +23,7 @@ def _ssn_pattern(ssn_check_marks: str): (?P(0[1-9]|[1-2]\d|3[01]) (0[1-9]|1[012]) (\d{{2}})) - [ABCDEFYXWVU+-] + (?P[ABCDEFYXWVU+-]) (?P(\d{{3}})) (?P[{check_marks}])$""".format(check_marks=ssn_check_marks), re.VERBOSE, @@ -95,6 +96,9 @@ def fi_ssn(value: str, /, *, allow_temporal_ssn: bool = True): if not (result := re.fullmatch(_ssn_pattern(ssn_check_marks), value)): return False gd = result.groupdict() + century = 1800 if gd["century"] == "+" else 2000 if gd["century"] in "ABCDEF" else 1900 + birth_date = gd["date"] + date(century + int(birth_date[4:]), int(birth_date[2:4]), int(birth_date[:2])) checksum = int(gd["date"] + gd["serial"]) return ( int(gd["serial"]) >= 2 diff --git a/tests/i18n/test_fi.py b/tests/i18n/test_fi.py index 64f479cf..08daef4f 100644 --- a/tests/i18n/test_fi.py +++ b/tests/i18n/test_fi.py @@ -79,3 +79,26 @@ def test_returns_failed_validation_on_invalid_ssn(value: str): def test_returns_failed_validation_on_temporal_ssn_when_not_allowed(): """Test returns failed validation on temporal-ssn when not allowed.""" assert isinstance(fi_ssn("010190-900P", allow_temporal_ssn=False), ValidationError) + + +@pytest.mark.parametrize( + ("birth_date", "century", "valid"), + [ + ("310299", "-", False), + ("310499", "-", False), + ("290201", "A", False), + ("290200", "+", False), + ("290200", "-", False), + ("290200", "Y", False), + ("290200", "A", True), + ("290200", "F", True), + ("290204", "-", True), + ("290204", "U", True), + ], +) +def test_ssn_validates_birth_date_in_its_century(birth_date: str, century: str, valid: bool): + """Reject impossible birth dates even when the control character is correct.""" + serial = "010" + checksum = "0123456789ABCDEFHJKLMNPRSTUVWXY"[int(birth_date + serial) % 31] + value = birth_date + century + serial + checksum + assert bool(fi_ssn(value)) is valid