Skip to content

Releases: identique/idnumbers

Release 1.21.0

Choose a tag to compare

@AngusHsu AngusHsu released this 24 Oct 10:06
a9f6cc6

Validity changes

  • CAN (#635): SocialInsuranceNumber.validate() rejects
    prefixes 0 and 8 under the standard SIN rules, excluding the CESP-specific exception for primary-caregiver SINs
    beginning with 0. checksum() remains an arithmetic-only Luhn check.

Added

  • LUX entity TIN (#272): add LUX.EntityTIN, the 11-digit
    national identifier that is used as a TIN-like number for non-natural persons, with the weighted mod 11 check digit
    from the OECD Luxembourg TIN sheet; numbers whose remainder is 1 are rejected because it is never allocated. LUX.TIN
    groups individual (LUX.NationalID) and entity (LUX.EntityTIN). LUX.NationalID is unchanged.
  • DEU tax number (#130): add DEU.TaxNumber (alias
    DEU.Steuernummer), the Steuernummer in the 13-digit ELSTER format. It checks the state number, the district rules
    and the state-specific check digit (11er-Verfahren, 2er-Verfahren or the modified 11er-Verfahren of
    Rhineland-Palatinate, with the Berlin-A and Berlin-B weights) from the ELSTER document "Prüfung der Steuer- und
    Steueridentifikationsnummer", and parse() returns the state, tax office, district and distinguishing number. The
    format printed on tax notices is not accepted. DEU.NationalID is unchanged.

Other fixes

  • API docs deployment (#644): the apidoc workflow queues its
    deploys in a concurrency group (apidoc-dev for pushes to main, apidoc-<version> for a release) and rebases and
    retries a rejected gh-pages push, so back-to-back pushes to main no longer fail the docs deploy and a push to
    main never cancels a release's docs deploy.
  • FRA INSEE (#637): INSEE.parse() (and
    FRA.NationalID.parse()) returns None for a number with a wrong control key, which validate() already rejected.
    Valid numbers parse as before.
  • NOR (#637): NationalID.parse() returns None for a number
    with wrong check digits, which validate() already rejected. validate() results and idnumbers.parse_id_info()
    are unchanged.
  • Parity corpus (#636): vendor the Node port's parity corpus
    at port commit 782680d, which adds CRI, DOM, ECU, EGY, GTM, RUS and SAU (85 countries, 20,672 vectors), and record
    Python's validity for them, so a validity change in those seven countries now fails tests/parity/test_corpus.py.
    Python's recorded validity for the other 78 countries is unchanged.

Release 1.20.0

Choose a tag to compare

@AngusHsu AngusHsu released this 13 Oct 03:01
e36d6e4

Validity changes

  • BRA (#617): RG checksum validation treats X as value 10,
    rejecting it when the weighted sum has remainder 0 modulo 11 and accepting it when the remainder is 1. Numeric
    check-digit behavior is unchanged; remainder 0 still accepts 0.
  • UKR (#616): taxpayer ID numbers with encoded birth dates
    after the local current date now return False from validate() and None from parse(). Today's date remains
    accepted, and checksum computation and parsed fields for past dates are unchanged.

Changed

  • Coverage badge (#600): the README shows the line and branch
    coverage from a shields.io endpoint JSON that CI publishes to the badges branch on pushes to main, using
    GITHUB_TOKEN with no repository secrets or external service. Pull request CI and the 97% floor are unchanged.
  • Coverage badge hardening (#611): CI tests pin the workflow
    triggers to push and pull_request, the coverage checkout does not persist credentials, and badge publication
    skips stale main commits after checking the remote head. The endpoint JSON, coverage metric and floor are unchanged.
  • Formatting (#599): the Python sources are formatted with
    ruff format (ruff 0.16.10, 120 columns; Markdown is left alone) and CI checks them with ruff format --check.
    .git-blame-ignore-revs lists the reformat commit, so git blame skips it. Code and behaviour are unchanged. This
    also settles the quote style of the README example tests
    (#561).

Other fixes

  • Checksums (#619): every public checksum() returns
    False or None for non-string input instead of raising or computing a value. HKG and TUR computed checksums
    now return Optional[str]. All string-input behavior, including malformed strings, is unchanged.
  • Formatting (#618): normalize_id() and format_id()
    uppercase ASCII letters only, leaving non-ASCII letters (including lowercase Greek letters) unchanged instead of
    folding lookalikes or expanding ligatures into ASCII. Helper outputs change; country validation is unchanged.
  • LKA (#615): old NIC suffixes V/v/X/x historically
    indicated voter/nonvoter status, not citizenship. OldNationalID.parse() keeps the citizenship key but returns
    None instead of citizen/resident status, with type Optional[Citizenship]. Validation, checksum, conversion and
    other parsed fields are unchanged.

Documentation

  • Contributor checklists (#608): explicitly name both ruff
    lint and formatting checks. CI contract tests tolerate extra formatter exclusions and reordered jobs while
    retaining readable failures for missing checks. Clarify checksum-group coverage, line wrapping and the first
    trusted release wording; CI policy and library behaviour are unchanged.
  • Contributor guide (#598): the line-length rule in
    AGENTS.md now says that every line of a reflowed paragraph counts as changed, and that only URLs are exempt: other
    long string literals and citations are wrapped. Code and behaviour are unchanged.
  • Country template (#595): the checksum group item names the
    check character(s) and covers a checksum() that returns a derived value (the tax file number's remainder): the group
    stays over the check character, and the class needs the named exemption in TestComputedChecksumGroups. The BGR
    13-digit test comment now names digits 9 to 12. Code and behaviour are unchanged.
  • Input masks (#620): clarify that masks describe preferred
    shapes, not fixed letter prefixes, ID validity or every accepted separator variant. Tests check accepted mask
    representatives directly against each class's regexp as well as its validator. Metadata and helper outputs are
    unchanged.
  • Release procedure (#603): RELEASE.md dates the closure
    of #327 (2026-10-12) and says that releases since 1.16.0 have
    run the updated workflow runtimes, with 1.18.0 the first trusted publication. The procedure is unchanged.

Release 1.19.0

Choose a tag to compare

@AngusHsu AngusHsu released this 12 Oct 08:49
9f092a2

1.19.0 (2026-10-12)

Documentation

  • Contributor guide (#581): the 120-column rule in
    AGENTS.md now applies to new and changed lines, and older long lines are not swept. A URL may also stay whole as a
    single string literal, such as an entry of METADATA.links. Code and behavior are unchanged.
  • Failure reasons (#578): the country template's checklist
    now asks for a checksum group in METADATA.regexp whenever checksum() returns the check digit or character, so
    failure_reason() can report checksum_mismatch; a bool checksum() needs no group. The BGR Unified
    Identification Code note in docs/FAILURE_REASONS.md now says that a 13-digit code whose first nine digits fail the
    9-digit check but whose 13th digit is correct stays validation_failed, because the group covers only the 13th
    digit. Validity and diagnostics are unchanged.

Release 1.18.0

Choose a tag to compare

@AngusHsu AngusHsu released this 12 Oct 07:04
5c43c37

1.18.0 (2026-10-12)

Changed

  • Failure reasons (#506): METADATA.regexp of 14
    computed-checksum classes (AUS Medicare Number and Tax File Number, BGR Unified Identification Code, CHL National
    Unique Role Number, COL Unique Personal ID, GRC Tax Identity Number, HKG Hong Kong Identity Card Number, IRN National
    ID Number, ISR Identity Number, JPN My Number, LVA Personal Code, TUR National ID Number, UKR Legal Entity ID Number
    and Taxpayer ID Number) now names the check character(s) with a checksum group, so failure_reason() reports
    checksum_mismatch instead of validation_failed for a definite check-character mismatch. The unified
    validate() and parse_id_info() report it too for the nine classes that are their country's primary ID (CHL, COL,
    HKG, IRN, ISR, JPN, LVA, TUR and the UKR Taxpayer ID Number); they select only the primary ID, so the other five
    (AUS Medicare Number and Tax File Number, BGR Unified Identification Code, GRC Tax Identity Number and UKR Legal
    Entity ID Number) are reached through failure_reason(id_class, number). Accepted and rejected inputs, parsed
    results and checksum() values are unchanged. The Australian tax file number's checksum() returns a remainder
    rather than a check digit, so a remainder equal to the last digit keeps the generic reason.

Other fixes

  • Parity sync script (#564): construct failed-rollback reports
    before bytecode cleanup and test that a failed restore after KeyboardInterrupt or SystemExit reports both errors
    and the tracked paths with exit code 2; successful rollback still propagates the original interrupt.
  • Registry tests (#566): exercise every slow key lookup path
    in the concurrency helper and check the portable non-iterable alias error fragment without changing registry behavior.
  • Release tests (#559): cover rejection of a bare
    mismatched-version dist-info entry and an absolute package path in wheels; release guard behavior is unchanged.

Documentation

  • Contributor guide (#551): AGENTS.md documents the
    120-column line-length convention and its exceptions (whole URLs on their own line, Markdown tables and generated
    guides); the 1.17.0 DOM entry now uses python-stdnum's own wording.
  • Contributor guide (#561): specify that the re-export
    exemption applies to from .util import ..., not from . import util; only the rule's wording changes.
  • Display format (#557): clarify that capital-letter field
    names are outside {...} and D usually denotes a day, with widths and meanings defined per class; formats and
    validity are unchanged.
  • EGY national ID number (#555): distinguish the Node
    sister project's reported Luhn failure from its unproven weighted mod-11 hypothesis in the docstring; the research
    note is not an official source, and validity is unchanged.
  • Mask vocabulary (#570): explain Sweden's retained +
    exception to slot-count matching, with a synthetic layout example; preferred input-mask patterns still use literal
    -, and formatting behavior is unchanged.
  • Unified API tests (#568): document the hostile
    representation helpers with class docstrings; test behavior is unchanged.

Release 1.17.0

Choose a tag to compare

@AngusHsu AngusHsu released this 11 Oct 18:39
646eb63

Other fixes

  • Metadata follow-up (#540): independently pin metadata
    discovery counts, approved display formats and official names in tests; report JSON scans of modules without ID
    classes as clean CLI usage errors. Clarify length-only formatting, refresh Cyprus government/OECD source links
    and use an official Swedish test personal number in formatting tests. Country validation behaviour is unchanged.
  • Packaging (#529): the release check now also rejects a
    wheel entry that is a bare file named idnumbers or idnumbers-<version>.dist-info rather than a path inside that
    directory; the published wheel is unchanged.
  • Parity sync script (#533): when --from-port fails and
    restoring the tracked files also fails, both errors are reported and the possibly half-updated files are named;
    tests now cover drop_bytecode() and keep the non-git port case from finding an enclosing repository.
  • Unified API (#538): validate(), validate_many() and
    parse_id_info() no longer raise when a country's repr() returns a str subclass whose length or slicing raises;
    the README describes the error_message fallbacks.

Documentation

  • CRI cédula de identidad (#515): correct the example's
    provenance to the Node port and add tests for compact, fully hyphenated, mixed and malformed hyphen layouts;
    validity and parsed results are unchanged.
  • DOM cédula de identidad y electoral (#519): state the
    exact size of python-stdnum's list of issued numbers that fail Luhn (578 entries: 576 with 11 digits, 2 with 10
    digits) and quote its own description of them; validity is unchanged.
  • ECU cédula de identidad (#521): drop the unsourced RUC
    remark, describe the Gob.EC issue on the third digit 6 as reported validation failures, and cite python-stdnum's
    actual hyphenated example 171430710-3; validity is unchanged.
  • EGY national ID number (#523): attribute the Luhn and
    mod-11 check-digit claims to third-party sources and cite the Node port's research note; the failure-reason guide
    now calls its date witnesses impossible-date witnesses, since not every one has a checksum; validity is unchanged.
  • GTM, SAU (#527): define the display_format
    placeholder letters in the class docstrings, and explain in docs/nationalid/METADATA.md that letters other than
    # (and, in a few classes, L) name per-class fields; formats are unchanged.
  • Narrow the AGENTS.md re-export rule to the single-dot relative imports that tests/test_explicit_reexports.py
    checks, and unit-test the checker (#531).
  • Say in the register() docstring that a non-iterable aliases raises TypeError, and make the registry
    concurrency tests fail when the registration lock is missing
    (#535).
  • Unified API (#543): render builtin TypeError names as
    literals in result-class hash documentation, avoiding unresolved API documentation links; behaviour is unchanged.

Release 1.16.0

Choose a tag to compare

@AngusHsu AngusHsu released this 11 Oct 15:45
b327bba

Added

  • CRI cédula de identidad (#329): add the 9-digit Costa Rica
    identity card number P-TTTT-AAAA (province 1-9, tomo, asiento), with optional hyphens and no check digit;
    parse() returns the province, its name, the tomo and the asiento. python-stdnum's 10-digit 0P-TTTT-AAAA form is
    not accepted.

  • DOM cédula de identidad y electoral (#330): add the
    11-digit Dominican Republic identity and electoral card number NNN-NNNNNNN-C with optional hyphens and a Luhn
    check digit; parse() returns the series, the sequence and the check digit. python-stdnum's list of issued numbers
    that fail Luhn is not accepted (#517).

  • ECU cédula de identidad (#331): add the 10-digit Ecuador
    identity card number: province 01-24 or 30 (registered abroad), a third digit 0-6 (4 or 5 for province 30) and a
    mod-10 check digit, with an optional hyphen before the check digit; parse() returns the province, the sequence
    number and the check digit.

  • EGY national ID number (#332): add the 14-digit Egypt
    national number CYYMMDDGGSSSSV (Executive Regulations of the Civil Status Law, Art. 3): century 2 or 3 and a real
    birth date are validated; the governorate code, the serial and the final digit are not (no official code table or
    check-digit algorithm is published). parse() returns the birth date, the governorate code, the serial and the
    final digit.

  • GTM Código Único de Identificación / DPI (#333): add
    structure-only validation of compact 13-digit CUI numbers (department 01-22, municipality 01-99). parse()
    preserves the eight-digit correlative, unverified check digit, department and municipality as strings. No
    checksum or per-department municipality table is enforced; whitespace and separators are rejected.

  • RUS internal passport (#334): add structure-only
    validation of a four-digit series and six-digit number, rejecting all-zero fields by maintainer policy.
    Accept only compact ASCII digits, SSSS NNNNNN or printed SS SS NNNNNN with single ASCII spaces;
    parse() preserves series and number as strings. No checksum or issuance/authenticity check is performed.

  • SAU national ID / Iqama (#335): add structure-only
    validation of the 10-digit Saudi Arabia national ID and resident identity (Iqama) number: the first digit is 1
    (citizen) or 2 (resident), per the NPHIES Patient profile. parse() returns the citizenship. No check digit is
    verified, because the Luhn rule is unconfirmed by any authoritative source; only compact ASCII digits are
    accepted.

Changed

  • Release workflows (#468): update the version-bump and
    documentation actions to Node.js 24 runtimes and pin Ubuntu 24.04, preserving the bot PR and docs deployment
    contracts and the existing release authentication routes.
  • Metadata (#501): the official names of COL (NUIP), CYP
    (ΑΦΤ) and IRN (شماره ملی) are corrected against official sources. display_format now uses [...] for an optional
    part and {...} for exactly one of the listed characters, and keeps parentheses as printed characters (BGR, HKG,
    IRL, AUS Medicare and FIN). docs/nationalid/METADATA.md documents both, and that masks are the preferred layouts
    the class accepts, not necessarily every layout it accepts.
  • Packaging (#484): the wheel no longer installs the
    repository's top-level tools package or the <prefix>/version/VERSION data file; it now ships only the
    idnumbers package. The source distribution is unchanged, and the library API is unaffected. The release check
    rejects a wheel with any other top-level entry.

Other fixes

  • Tests (#503): treat an empty Hypothesis storage override
    as unset, keeping ancillary caches in temporary storage while preserving the original environment value.
  • Registry (#496): register() no longer drops aliases
    passed as a one-shot iterable such as a generator, and aliases is now typed Iterable[str]; resolve_country(),
    and so get_country() and get_validator(), no longer raises for a str subclass that overrides isascii() or
    upper() or for an object that fakes its __class__; register() rejects a blank name and gives a missing
    alpha-2 code its own error message.
  • Scan tool (#501): tools.scan_ids.collect_ids() lists only
    the ID classes defined in its package_name argument, which it used to ignore, and raises ValueError when the
    package defines none. The default idnumbers.nationalid output is unchanged.
  • Unified API (#499): validate() and parse_id_info() no
    longer raise when the country's repr() raises or when the exception of a validator or parser can't be turned into
    text, and the unsupported-country message shortens a long country value to 80 characters. The ValidationResult
    and ParseSuccess docs say that a result with parsed data isn't hashable.

Documentation

  • Add a local rendered API href checker and hermetic regressions for complete parenthesized URLs, fragments and
    explicit-target continuations (#463).

  • Document the explicit re-export form for country modules and the pinned coverage version in AGENTS.md
    (#487).

Release 1.15.0

Choose a tag to compare

@AngusHsu AngusHsu released this 08 Oct 01:38
11088b0

Added

  • Add generated country metadata, failure-reason and input-format guides, executed and strictly typed documentation
    examples, and contributor/country workflow templates
    (#328).

  • Add primary-only normalize_id(), format_id() and get_input_mask() with frozen InputMask results.
    Metadata-driven formatting checks length, not validity; input patterns use ASCII uppercase letters/digits.
    Swedish formatting preserves a significant + in both 10-digit and 12-digit forms; preferred mask patterns
    remain literal minus layouts. Greece's primary metadata example now uses already-accepted Latin letters;
    Greek validation and formatting remain unchanged (#321).

  • Add failure_reason(id_class, id_number) for primary and secondary ID types. Unified validation and parsing now
    report best-effort length, format, checksum and embedded-calendar-date reasons; inconclusive checks and exceptions
    retain VALIDATION_FAILED. Date tracing is isolated between nested calls, threads and async tasks, and country
    validity, parser and checksum behavior is unchanged (#322).

  • Add idnumbers.parse_id_info(country, id_number) for a country's primary NationalID, returning the frozen
    ParseSuccess / ParseFailure union ParseIdInfoResult. The ok discriminator supports strict type narrowing;
    success includes a fresh dictionary of the country parser's information. Lookup and validation failures match
    validate(), while valid IDs without extractable information give NOT_PARSABLE. Country parsers are unchanged
    (#319).

  • Add a country registry, idnumbers.registry, which is re-exported from idnumbers. get_validator('tw'),
    get_country('TWN') and resolve_country('tw') find the NationalID class, a CountryEntry (alpha-3 and alpha-2
    code, name, NationalID and all ID types) or the alpha-3 code of a country by its alpha-2 or alpha-3 code in any
    letter case, and give None for anything unknown instead of raising. list_supported_countries() lists the 78
    countries, and register() adds a custom country. The names are the English names of Unicode CLDR 48.2.3. AUS and
    GRC keep their current NationalID, and a country module is imported on the first lookup of its country
    (#318).

  • Add idnumbers.validate(country, id_number), which validates an ID number of any supported country and returns a
    frozen ValidationResult with is_valid, the alpha-3 country_code, the id_number, the extracted_info of a
    valid ID number (the parse() result, None when the ID type isn't parsable), a reason and an error_message. It
    never raises, and a result is truthy when it is valid. An unknown country gives the reason unsupported_country,
    other failures a best-effort diagnostic reason. validate_many() validates (country, id_number) pairs, and
    FailureReason lists the reasons, which later releases may extend. The country classes are unchanged
    (#317).

  • Describe every ID class with seven more METADATA keys: country_name, id_type, official_name (None for an
    ID with no local-language name), display_format, example (a synthetic number the class validates),
    checksum_algorithm (None without a check digit) and masks, the layouts the class accepts. METADATA is now an
    IdMetadata, a typed SimpleNamespace subclass in idnumbers.nationalid.metadata, so attribute access, vars(),
    copy() and getattr() work as before. A class made by alias_of, such as AUS.NationalID, now has the name,
    module and docstring of its target instead of AliasType, which changes its repr. CAN.SocialInsuranceNumber
    gets the deprecated key the other classes already had, and tools.scan_ids lists the classes through the country
    registry (#320).

Changed

  • Ship the PEP 561 py.typed marker, so type checkers use the package's annotations, and correct the annotations
    that disagreed with the runtime values: the CHECK_DIGIT alias, the untyped normalize and checksum helpers,
    the COL and KAZ checksum methods that return False for malformed input, the AUS TFN and KWT civil-number
    checksum methods, which can return 10, and the LTU extract_year_base_gender result, which is never None
    (#325).

  • Move the build metadata from setup.py to a PEP 621 pyproject.toml. The license is now the SPDX expression MIT
    instead of the deprecated license classifier (#465), and
    building from source needs setuptools 77 or later, which pip and uv install automatically
    (#325).

  • CI type-checks the package with strict mypy for Python 3.9 and checks the built distributions with
    twine check --strict (#325).

  • Re-export the ID classes explicitly (import X as X) from the country modules such as AUS, so strict type
    checkers (mypy --strict, which disallows implicit re-exports) accept
    from idnumbers.nationalid.AUS import MedicareNumber and AUS.MedicareNumber; nothing changes at runtime
    (#326).

  • Prepare isolated, guarded PyPI publication and opt-in Trusted Publishing with attestations; retain the working
    default token route and existing secrets. External publisher setup and the first verified trusted release remain
    pending maintainer action. Document release ordering and 1.14.0-to-1.15.0 migration
    (#327).

  • Add dev-only Hypothesis checks of arbitrary Unicode inputs for every registered ID type, metadata method
    contracts, out-of-range lengths and checksum mutations, with explicit synthetic witnesses for checksum limits
    (#324).

  • CI lints with ruff, enforces a 97% line-and-branch coverage floor, runs the README Python examples and
    informational benchmarks, keeps GitHub Actions updated with Dependabot, and reports a single ci-summary result
    (#326). Formatting with ruff format and a coverage badge
    await a decision in #485.

  • Vendor the Node port's parity corpus (78 countries, 17,960 vectors) at a pinned port commit and record Python's own
    validity for it, so a validity change on any shared vector fails a test and gets a port note;
    scripts/sync_parity_corpus.py refreshes both (#323).

Release 1.14.0

Choose a tag to compare

@AngusHsu AngusHsu released this 07 Oct 10:28
e252813

Documentation

  • Add an optional uv contributor quickstart with editable installation and full-suite or single-country tests
    (#473).

  • Check published PyPI packages with uv on Python 3.9 and 3.14 on demand, and document the post-release check
    (#472).

  • Add a runnable uv script and isolated wheel-install CI checks for the script and README one-liner
    (#471).

  • Document project, virtual-environment, one-off and standalone-script usage with uv in the README
    (#470).

Release 1.13.0

Choose a tag to compare

@AngusHsu AngusHsu released this 07 Oct 08:53
0587954

1.13.0 (2026-10-07)

Validity changes

  • ALB identity number (#344): decoded birth dates after today
    are rejected, using the date rather than a fixed decade cap. The existing historical year range is unchanged,
    pending confirmation in #446.

  • ARG DNI (#280): 7-digit numbers (below 10 million), such as
    5.123.456 and 5123456, are now accepted. METADATA.min_length is 7.

  • AUT entity UID (#345): require the U prefix;
    other letters are rejected by validate() and the input guard in checksum(), even with a correct
    check digit. The checksum arithmetic and supported separators are unchanged.

  • BRA CPF (#284): numbers made of one repeated digit,
    000.000.000-00 to 999.999.999-99, are now rejected. They pass the check digits, but the federal civil-registry
    system SIRC lists repeated digits as a reason for an invalid CPF. checksum() is unchanged.

  • CHN resident ID (#347): accept ASCII lowercase x as the
    final check character; parsed and calculated check characters remain canonical X or integers. Checksum arithmetic,
    calendar validation and region policy are unchanged; region/issuance checks remain deferred to
    #451.

  • CYP tax number (#348): prefix 12 is rejected;
    checksum() remains unchanged.

  • DEU tax ID (IdNr) (#289): the digit rules now follow the
    ELSTER specification. A leading 0 is rejected, which includes ELSTER test numbers such as 02476291358. Among the
    first ten digits, exactly one digit must appear twice or three times, so 39825979193 (four 9s) and 36794081522
    (no repeated digit) are rejected. A check digit equal to the two digits before it is no longer rejected, so
    38056471999 is now valid.

  • DNK CPR number (#290): the century now comes from the
    seventh digit and the two-digit year, as the CPR office's table defines it, instead of yy > 50. parse() reads
    0101451234 as 1945-01-01 instead of 2045-01-01, and 1858-1899 births are recognised. 2902000000 (29 February 1900)
    is now rejected and 2902004000 (29 February 2000) is valid. A birth date in the future is rejected.

  • ESP DNI (#349): validate() and checksum() now accept
    ASCII lowercase check letters and strip surrounding whitespace. Checksum arithmetic and the eight-ASCII-digit
    plus ASCII-letter payload format are unchanged; internal whitespace and separators remain invalid.

  • FRA NIR (#291): birth department 96 is now accepted. It
    was used for Tunisia before 1964, like 91-95 for Algeria and Morocco, so 145089612304582 is now valid.

  • IDN NIK (#293): encoded birth days are now validated as
    01-31 for men and 41-71 for women. Impossible dates are rejected, while 29 February with year 00 is
    accepted because it exists in 2000 even though it does not exist in 1900; NIK does not identify the century.

  • IRL PPS number (#294): the optional second suffix character
    is now limited to A, B, H, W or a space; other letters are rejected even when the check character is correct.
    The whitespace restriction was already fixed in #369.
    Historical T/X suffix handling remains out of scope and is tracked in
    #433.

  • IRN national ID (#295): codes made of one repeated digit,
    0000000000 to 9999999999, are now rejected, including supported hyphenated formats. checksum() is unchanged.

  • ISR identity number (#350): reject 000000000 even though
    it passes Luhn. checksum() is unchanged; the exact nine-digit format remains required. Shorter-number padding
    policy is deferred to #455.

  • KAZ BIN (#297): registration months outside 01-12 are
    now rejected, including checksum-correct 750061381659 (month 00). checksum() is unchanged.

  • LKA NIC (#299): both old and new formats now reject encoded
    birth-day values outside 001-366 for men and 501-866 for women, even with a correct checksum.
    checksum() and within-range date decoding are unchanged; leap-year-specific decoding remains out of scope in
    #437.

  • LTU personal code (#300): century decoding now accepts
    50002290013 (29 February 2000) and rejects 70002290015 (29 February 2100). checksum() is unchanged.
    First-digit policy for 0 and 9 remains out of scope in #337.

  • LVA personal code (#301): legacy codes now require a valid
    calendar date and century digit 0/1/2; unsupported prefixes 00 and 40-99 are rejected even with a correct
    check digit. Date-free modern prefixes 32-39 remain accepted per current primary sources. checksum() is
    unchanged; the modern checksum requirement remains out of scope in
    #440.

  • MAC identity number (#351): parentheses around the final
    digit must be paired; unbalanced forms are rejected. Compact eight-digit and printed parenthesized forms remain
    accepted, with unchanged parse payloads and eight-significant-digit length metadata.

  • MEX CURP (#352): validate() and parse() reject
    the 81 original inconvenient name prefixes in RENAPO's catalogue. Their second-letter X replacements remain
    accepted when otherwise valid; standalone checksum() is unchanged.

  • NLD BSN (#303): compact nine-digit numbers such as
    123456782 are now accepted, while the existing fully dotted layout 1234.56.782 remains accepted. All-zero
    numbers remain rejected in both formats; the 11-proof and checksum() are unchanged.

  • NZL (#306): the expanded NHI checksum now uses modulus
    23 and check-letter indices 1-23, per HISO 10046:2024; the legacy numeric checksum is unchanged. Driver licence
    prefixes now require two ASCII letters, preserving lowercase acceptance and the trailing-digit blacklist. IRD
    numbers must be within the current inclusive 10,000,000-200,000,000 range and pass the existing checksum.

  • PRT (#353): add CitizenCard for compact 12-character
    document numbers with the full-card checksum. CivilIDNumber and NationalID remain BI/NIC-only. NIF now accepts
    prefixes 74, 75, 77, 78 and 79; its checksum arithmetic and other prefix rules are unchanged.

  • SWE (#311): personnummer now accepts 10- and 12-digit
    compact forms and optional -/+ separators; explicit four-digit years determine the century. Only the two
    century digits are excluded from the checksum. Add separate CoordinationNumber support for samordningsnummer,
    with encoded birth day +60 and decoded dates in parse(). NationalID remains personnummer-only; existing
    10-digit -/+ decoding and checksum-calculator behavior are unchanged.

  • UKR EDRPOU (#312): a second-pass remainder of 10 now
    maps to check digit 0, so 57334830 and 43089360 are accepted. Existing weight selection is unchanged;
    the separate selection question remains tracked in #342.

Other fixes

  • ALB identity number (#344): the missing metadata comma is
    restored, separating Numri i Identitetit të Shtetasit and NISH into the intended six names.
  • BGD national ID (#346): parse() returns yyyy as
    the declared four-character string, preserving leading zeros. The national ID metadata lengths are corrected to 17;
    old national ID metadata remains 13.
  • BIH (#354): correct the JMBG class country label to
    Bosnia and Herzegovina; validation and parsing are unchanged.
  • CHN resident ID (#419): add regression coverage for
    October birth dates, already fixed in #275;
    validity is unchanged.
  • FRA NIR (#291): parse() now reports gender code 7, the
    temporary number for a man, as male. Codes 3 and 4 are still reported as female, because no source says which sex
    they stand for (#429).
  • GRC (#354): correct new identity-card length metadata to
    eight significant characters. Old-card metadata remains seven; option...
Read more

Release 1.12.0

Choose a tag to compare

@AngusHsu AngusHsu released this 07 Oct 02:23
6520de4

The first release since 1.11.0. It fixes validity bugs that were found by comparing the library with its Node.js port
and with python-stdnum. Every fix cites its source in the class docstring or in METADATA.links. Several fixes change
which IDs are accepted, so read the first two sections before you upgrade.

Behaviour changes

  • validate() never raises any more. It returns False for non-str input, for empty input and for malformed input,
    and parse() returns None for the same inputs. Before, most types raised AssertionError, ValueError,
    IndexError or TypeError (#371).
  • An integer is no longer converted to a string. ALB, ARE, BHR, CHN, EST, ITA, KWT, LKA (old), LTU, POL and VNM used to
    turn non-str input into text with repr(), so LTU.NationalID.validate(37605030299) was True. It is now False,
    like every other type. Convert integers with str() first (#371).
  • The whole input must match, with ASCII digits only. A trailing newline, and digits such as Arabic-Indic ١٢٣ or
    full-width 123, used to slip through the patterns and are now rejected
    (#369). The Irish PPS number allows only a space after the number,
    not other whitespace. A tab or newline there used to raise and now gives False
    (#369).
  • checksum() has a documented contract for malformed input. Checksums that return a digit or letter return None, and
    checksums that return a bool return False, instead of raising or returning a meaningless value:
    • BGR, BRA (CPF and RG), IRN and JPN: None or False for input that does not match the format. For example,
      JPN.MyNumber.checksum('00000000178') was '3' and is now None
      (#371).
    • ZAF: None for input that does not match the ID format (#394).
    • TWN: None instead of False (#396,
      #414).
    • CHL: None instead of raising or returning a meaningless character
      (#410).
    • ZWE: False instead of raising AttributeError (#415).
    • Callers that used checksum() to compute a check digit from a prefix that does not match the ID format are
      affected.
  • checksum() results that were wrong are corrected: TWN returns 0 instead of 10
    (#396), BGR EGN returns 0 instead of 10
    (#375), and util.ean13_digit now weights the even positions by 3
    as EAN-13 does (#377). CHE BusinessID gains a checksum() method
    (#377).
  • parse() returns None for IDs that are valid but have no complete birth date or no check digit to report: BEL
    numbers with an unknown month or day (#373), and CZE and SVK
    9-digit birth numbers (#384). ParseResult is unchanged. BEL
    numbers with yy 50 now parse to 1950, not 2050 (#373).
  • Some types now accept or reject whole groups of IDs. The largest shifts are GEO (11 digits instead of 9,
    #400), ZWE (a new check letter,
    #398), CHL 7-digit RUTs
    (#382), CHE AHV numbers
    (#377) and TWN
    (#396). See "Validity changes".

Validity changes

Each bullet says what is accepted now and what is rejected now. Vectors are taken from the pull requests, and most of
them are also tests.

  • BEL national registration number (#373):
    • The post-2000 check digit was computed with an operator-precedence bug. Real numbers born from 2000, such as
      01010100126, were rejected, and any yy below 50 with check number 29 was accepted (00010100129, now
      rejected).
    • The century is inferred from the check digit, as the Rijksregister instruction IT000 requires. 1900-1949 numbers
      such as 47010100190 are now valid. A future 20xx birth year is rejected.
    • For yy 50, validity is unchanged, but parse() now returns 1950 instead of 2050 (50010100156 was read as
      2050-01-01 and is now 1950-01-01).
    • A month 00 or a day 00 (an incomplete birth date, for example 85003003376) is now valid and parse() returns
      None. A day that does not exist in its month, and a month above 12, stay invalid.
  • BEL entity VAT (#373): a 10-digit number must start with 0 or
    1. A 9-digit number may start with any digit.
  • BGR EGN (#375): a weighted-sum remainder of 10 gives check
    digit 0. About 1 in 11 real EGNs, such as 8507300050 and 0001011000, were rejected before.
  • BGR 13-digit EIK/BULSTAT (#375): the check now weights digits 9
    to 12, and the first nine digits must be a valid 9-digit EIK. 0114806291686 is valid and 0114806291688 is not.
    9-digit codes are unchanged.
  • CHE AHV/AVS number (#377):
    • The check digit uses the EAN-13 weights 1 and 3. Genuine numbers such as 756.9217.0769.85 are now accepted and
      wrong ones such as 756.0000.5678.17 are rejected.
    • The dot in the pattern is escaped, so 756.1234.5678-97 is False.
  • CHE UID (BusinessID) (#377): the modulus 11 check digit is
    verified. CHE-116.281.710 is valid and CHE-116.281.715 is not.
  • CHL RUT (#382,
    #410):
    • The weights run from the right, so 7-digit RUTs are checked correctly. 1.111.111-4 is valid, 1.111.111-3 is not.
      8-digit RUTs are unchanged.
    • A lowercase check character is accepted: 10.000.013-k is valid.
  • CZE and SVK birth number (#384):
    • Check digit 0 is accepted when the first nine digits leave remainder 10: 5401031230 is valid.
    • 9-digit numbers are accepted for births up to 1953. They need a valid date with yy 53 or below, for example
      530101123.
    • A 10-digit number with yy 00 to 53 is read as 2000 to 2053 and a birth date in the future is invalid. 5001010003
      is no longer read as 1950. METADATA.min_length is now 9.
  • ITA fiscal code (#386):
    • Omocodia codes are now valid. RSSMRA8LM01H501W used to make validate() raise.
    • parse() maps the letters back to digits (area_code is H500 instead of H50L) and picks the century: 20yy
      unless that date is in the future, otherwise 19yy. RSSMRA40M01H501B is born in 1940. Validity does not change for
      IDs without omocodia letters.
  • KWT civil number (#388): a number with a correct check digit
    but an impossible birth date (200022900006, 200013200008, 200001000009) used to raise. validate() is now
    False and parse() is None.
  • NOR national ID (#389):
    • An impossible date (31 April, 30 February, day or month 00, 00000000000) gives False instead of raising.
    • D-numbers (day plus 40, for example 41019912351) and H-numbers (month plus 40, for example 01419912340) are now
      valid and parse() returns the real birth date. A number with both offsets, and FH-numbers, stay invalid.
  • ROU personal numerical code (#391): the first digit 6 (women
    born 2000 to 2099) no longer raises, and 6050315121230 is valid. The first digit 2 now means 1900 to 1999 and 4
    means 1800 to 1899, so 4000229011237 (29 February 1800) is invalid.
  • SGP NRIC/FIN (#392): M-series FINs add a start constant of 3 to
    the weighted sum. M1234567K and M3960741N are now valid, and M1234567N is not. The S, T, F and G series are
    unchanged.
  • ZAF national ID (#394,
    #275): non-numeric input such as abc raised ValueError and now
    gives False. IDs with month 10 are accepted too, see the CHN and ZAF month pattern entry below.
  • TWN national ID (#396): a weighted sum that is a multiple of 10
    gives check digit 0. About 1 in 10 genuine IDs, such as M162773050 and A120229780, were rejected before.
  • ZWE national ID (#398): the check letter is the whole number
    modulo 23, as the cited source says, and not the digit sum. 502001148W50 and 082095850T42 are now valid.
    751919620S86 is now invalid, and 751919620J86 is the valid one. About 22 in 23 of the IDs that passed the old
    digit-sum rule are now rejected.
  • GEO personal number (#400): the number has 11 digits, and may
    start with 0. 01001011234 is valid. Every 9-digit string, which was valid before, is now invalid.
  • BRA RG and SWE personal identity number ([#402](ht...
Read more