PolishIdentifiers 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package PolishIdentifiers --version 1.0.0
                    
NuGet\Install-Package PolishIdentifiers -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PolishIdentifiers" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PolishIdentifiers" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="PolishIdentifiers" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PolishIdentifiers --version 1.0.0
                    
#r "nuget: PolishIdentifiers, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PolishIdentifiers@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PolishIdentifiers&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=PolishIdentifiers&version=1.0.0
                    
Install as a Cake Tool

PolishIdentifiers

NuGet CI License: MIT Targets: netstandard2.0 · net10.0

PolishIdentifiers is a .NET library that exposes PESEL, NIP, and REGON as strong identifier types (Pesel, Nip, Regon) instead of raw strings plus includes their strong validation.

These identifiers are implemented as tightly constrained readonly struct value types, with one unified Parse / TryParse / Validate API shape across the implemented surface, DataAnnotations attributes for request validation, generators for valid and intentionally invalid test values, broad unit-test coverage, and ReadOnlySpan<char>-based parsing and validation for low-allocation paths.

In practice, that gives you one package for parsing, validation, formatting, generation, and request-model validation while keeping strong identifier types throughout domain code.

Scope

This library covers strongly typed Polish formal identifiers — identity, registration, and business numbers used in Polish administrative and commercial contexts.

Currently implemented: Pesel, Nip, Regon.

Internally documented and planned for future releases: Polish bank account number (NRB), Polish ID card number, Polish passport number, land register number. These are not yet available as public types.

Framework support

Targets netstandard2.0 and net10.0.

The net10.0 build adds IParsable<T>, ISpanParsable<T>, and DateOnly-based PESEL members. See Framework support for all target-specific differences.

Supported identifiers

Type Identifier Accepted formats Docs
Pesel PESEL 44051401458 PESEL
Nip NIP 1234563218, 123-456-32-18, PL1234563218, PL 1234563218, PL 123-456-32-18 NIP
Regon REGON 123456785, 12345678512347 REGON

Parse, TryParse, Validate

Method Use when
TryParse You want a strong type and a non-throwing failure path; use the out TError? error overload to also receive the first structured validation error on failure
Parse Invalid input is exceptional and should throw an identifier-specific validation exception
Validate You only need a pass-or-fail result with the first structured validation error and do not need the typed identifier instance

Generators

<table> <thead> <tr> <th>Type</th> <th>Kind</th> <th>Outputs</th> <th>Docs</th> </tr> </thead> <tbody> <tr> <td rowspan="2"><code>PeselGenerator</code></td> <td>Valid</td> <td>PESEL with random or specified birth date and gender</td> <td rowspan="2"><a href="./docs/pesel-generator.md">PESEL generator</a></td> </tr> <tr> <td>Invalid</td> <td>invalid characters, wrong checksum, wrong date, wrong length</td> </tr> <tr> <td rowspan="2"><code>NipGenerator</code></td> <td>Valid</td> <td>canonical valid NIP</td> <td rowspan="2"><a href="./docs/nip-generator.md">NIP generator</a></td> </tr> <tr> <td>Invalid</td> <td>invalid characters, wrong checksum, wrong length</td> </tr> <tr> <td rowspan="2"><code>RegonGenerator</code></td> <td>Valid</td> <td>REGON-9 and REGON-14</td> <td rowspan="2"><a href="./docs/regon-generator.md">REGON generator</a></td> </tr> <tr> <td>Invalid</td> <td>invalid characters, wrong checksum for REGON-9, wrong checksum for REGON-14, wrong length</td> </tr> </tbody> </table>

Examples

Parse — when invalid input is a bug

using PolishIdentifiers;

var nip = Nip.Parse("1234563218");

Console.WriteLine(nip);                              // 1234563218
Console.WriteLine(nip.ToString(NipFormat.VatEu));   // PL1234563218

TryParse — non-throwing path with typed error

using PolishIdentifiers;

if (!Pesel.TryParse("44051401458", out var pesel, out var error))
{
    Console.WriteLine($"Rejected: {error}"); // e.g. InvalidChecksum
    return;
}

Console.WriteLine(pesel.BirthDate.ToString("yyyy-MM-dd")); // 1944-05-14
Console.WriteLine(pesel.Gender);                           // Male
using PolishIdentifiers;

if (!Nip.TryParse("PL 123-456-32-18", out var nip, out var error))
{
    Console.WriteLine($"Rejected: {error}");
    return;
}

Console.WriteLine(nip);                                   // 1234563218
Console.WriteLine(nip.ToString(NipFormat.Hyphenated));    // 123-456-32-18
using PolishIdentifiers;

if (!Regon.TryParse("12345678512347", out var regon, out var error))
{
    Console.WriteLine($"Rejected: {error}");
    return;
}

Console.WriteLine(regon.Kind);       // Regon14
Console.WriteLine(regon.BaseRegon9); // 123456785

Validate — check validity without allocating a typed instance

using PolishIdentifiers;

var result = Nip.Validate("123-456-32-18");

Console.WriteLine(result.IsValid); // True

var bad = Pesel.Validate("44051401459");

Console.WriteLine(bad.IsValid); // False
Console.WriteLine(bad.Error);   // InvalidChecksum

DataAnnotations attributes

using System.ComponentModel.DataAnnotations;
using PolishIdentifiers;

public sealed class InvoiceRequest
{
    [ValidNip]
    public string SellerNip { get; init; } = string.Empty;

    [ValidPesel]
    public string? BuyerPesel { get; init; }

    [ValidRegon]
    public string? SellerRegon { get; init; }
}

Generators

using PolishIdentifiers;

// Valid values for seeding test data
var pesel = PeselGenerator.Generate(Gender.Female, new DateTime(1990, 6, 15));
var nip   = NipGenerator.Generate();
var regon = RegonGenerator.Generate(RegonKind.Regon14);

Console.WriteLine(pesel); // e.g. 90061512345
Console.WriteLine(nip);   // e.g. 5261040828
Console.WriteLine(regon); // e.g. 12345678512347

// Intentionally invalid strings for negative test cases
string badPesel = PeselGenerator.Invalid.WrongChecksum();
string badNip   = NipGenerator.Invalid.WrongChecksum();
string badRegon = RegonGenerator.Invalid.WrongChecksumRegon9();

Common patterns

Deduplicating records by NIP

Nip is a value type with correct equality semantics. Two Nip values parsed from different format representations of the same 10-digit number are equal. Use a HashSet<Nip> or Dictionary<Nip, T> to deduplicate without format-specific string comparisons:

using PolishIdentifiers;

var seen = new HashSet<Nip>();

foreach (var raw in importedNipValues)
{
    if (!Nip.TryParse(raw.Trim().ToUpperInvariant(), out var nip, out _))
        continue;

    if (!seen.Add(nip))
        Console.WriteLine($"Duplicate NIP: {nip}");
}

Linking REGON branches to their parent company

REGON-14 identifies a local organizational unit. Its first 9 digits are the REGON-9 of the parent entity. Use BaseRegon9 to group branches under their parent:

using PolishIdentifiers;

var entities = new Dictionary<Regon, List<Regon>>();

foreach (var raw in importedRegonValues)
{
    if (!Regon.TryParse(raw, out var regon, out _))
        continue;

    var parentKey = regon.BaseRegon9; // equals regon itself for REGON-9
    if (!entities.TryGetValue(parentKey, out var units))
        entities[parentKey] = units = [];
    units.Add(regon);
}

See docs/patterns.md for persistence, import loop, and person-vs-company decision patterns.

Design philosophy

Domain properties on identifier types expose values that are direct structural decodes of the identifier's own digit fields: Pesel.BirthDate and Pesel.Gender are read directly from encoded digit positions in the PESEL number.

Derivations that require external state — such as calculating age from birth date, or checking eligibility based on a reference date — are consumer responsibilities. These are not added to the library. See docs/pesel.md for the recommended pattern.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on PolishIdentifiers:

Package Downloads
PolishIdentifiers.SystemTextJson

System.Text.Json converters for PolishIdentifiers. Adds JSON serialization support for PESEL, NIP, and REGON as strongly typed Polish formal identifiers. Targets netstandard2.0 and net10.0.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.1.0 111 4/17/2026
2.0.0 88 4/7/2026
1.0.0 89 3/22/2026
0.2.0 101 3/11/2026
0.1.1 89 3/8/2026
0.1.0 94 3/6/2026

First stable release. Pesel, Nip, and Regon are included as strongly typed identifiers with a unified Parse / TryParse(out error) / Validate API surface. NIP accepts canonical digits and five documented formatted representations (plain, hyphenated, PL-prefixed). Pesel exposes BirthDate and Gender. Regon covers both REGON-9 and REGON-14 with two-step checksum validation. All three include DataAnnotations attributes ([ValidPesel], [ValidNip], [ValidRegon]) and valid/invalid generators. Targets netstandard2.0 and net10.0. See CHANGELOG.md for the full change history.