Open Source

sri-lanka-nic.

A lightweight, zero-dependency TypeScript library to parse, validate, convert, and generate Sri Lankan NIC numbers.

YearFeb 21, 2026
AuthorVinod Liyanage
TypeScriptOpen SourcenpmVitestESMCJSTree-shakingZod
sri-lanka-nic

The Origin Story

Every Sri Lankan citizen carries a National Identity Card. The NIC number isn't just an ID — it's a compressed data structure that encodes birth year, birthday, gender, and voter status into 9 or 12 digits.

I needed to validate and parse these numbers in a project, and quickly discovered that every existing library just slaps a regex on it and calls it a day. A string that looks like a NIC passes validation — even if the encoded birthday is February 30th or the person would be 3 years old.

That wasn't good enough. So I built a library that actually understands what a NIC number means.


Beyond Regex

Most NIC validation libraries do a simple structure check. This library goes five layers deeper:

  1. Structure — matches old format (9 digits + V/X) or new format (exactly 12 digits)
  2. Birth year range — extracted year must fall between 1901 and a dynamically calculated upper limit
  3. Day-of-year bounds — validates the encoded day value (1–365 for males, 501–865 for females), accounting for leap years
  4. Leap year correctness — day 366 (or 866 for females) is only valid if the birth year is actually a leap year
  5. Minimum age requirement — checks down to the exact day, not just the year. If someone born on day 300 of the cutoff year hasn't turned 15 yet because today is day 200, the NIC is correctly rejected

This is validation that understands the domain, not just the format.


Parsing

One call to NIC.parse() gives you everything encoded in the number:

import { NIC } from "@sri-lanka/nic";
 
const nic = NIC.parse("853400937V");
 
nic.type;       // "old"
nic.gender;     // "male"
nic.birthday;   // { year: 1985, month: 12, day: 6 }
nic.age;        // 40
nic.serial;     // "093"
nic.checkdigit; // "7"
nic.letter;     // "V"
nic.voter;      // true

A 10-character string becomes a fully typed object with birthday, gender, age, serial number, check digit, and voter registration status.


Format Conversion

Sri Lanka has used two NIC formats over the years. The old format uses a 2-digit year, the new format uses 4 digits. Converting between them is a single method call:

// Old → New
NIC.parse("853400937V").convert();   // "198534009370"
 
// New → Old
NIC.parse("198534009370").convert(); // "853400937V"

The library handles all the encoding math internally — day-of-year offsets, gender encoding, serial numbers, check digits.


Random NIC Generation

Need test data? Generate structurally valid NIC numbers that will always pass validation:

const randomNIC = NIC.random();
// e.g. "941520456V" or "199815204560"

Every generated NIC uses real date logic — valid birth years, correct day-of-year ranges, proper leap year handling. Perfect for unit tests, database seeding, or mock data.


Error Handling

When something is invalid, you get a specific error — not a generic "invalid NIC" message:

try {
  NIC.parse("invalid-nic");
} catch (error) {
  if (error instanceof NIC.Error) {
    error.code;    // "INVALID_NIC_STRUCTURE"
    error.message; // descriptive error with context
  }
}

Error codes include INVALID_NIC_STRUCTURE, INVALID_BIRTH_YEAR, INVALID_DAY_OF_YEAR, and MINIMUM_AGE_REQUIREMENT_NOT_MET. If you prefer not to throw, use NIC.valid() for a boolean or NIC.validate() for a result object.


Zod Integration

Drop it straight into your form validation or API input schemas:

import { z } from "zod";
import { NIC } from "@sri-lanka/nic";
 
const schema = z.object({
  nic: z.string().refine((v) => NIC.valid(v), {
    message: "Invalid Sri Lankan NIC",
  }),
});
 
schema.parse({ nic: "853400937V" });   // ✅ passes
schema.parse({ nic: "0000000000" });   // ❌ throws ZodError

The Technical Details

MetricValue
Bundle Size< 6 kB minified
DependenciesZero
Module FormatsESM, CJS, TypeScript declarations
Tree-shakingFully supported
Test CoverageComprehensive with Vitest

Try It

pnpm add @sri-lanka/nic