
Have you ever wondered how much personal data is quietly baked into the string of digits on your National Identity Card?
In Sri Lanka, an Identity Card (NIC) number isn't an arbitrary sequence generated by a database โ it's a deterministic, compact demographic hash containing your exact birth year, day of birth, legal gender, and historical electoral eligibility.
For decades, converting an NIC number to a date of birth โ or just verifying an ID card โ meant manual calendar arithmetic, or handing your sensitive personal data to an ad-filled site that shipped it straight to some remote server.
I decided to fix that by building NIC Info, a high-performance, air-gapped, zero-dependency NIC decoder and conversion suite, built entirely with modern web standards.
Here's what I'll cover:
- The math and engineering behind Sri Lanka's NIC formats (old vs. new)
- The female
+500day offset and leap-year calendar quirks - Architecting an air-gapped, zero-knowledge client engine
- Designing a bespoke "Registry Office" editorial UI system
- Key takeaways for building high-performance web apps in 2026
๐๏ธ 1. The Anatomy of Sri Lankan Identity Numbers
Sri Lanka has two distinct NIC numbering eras, administered by the Department for Registration of Persons (DRP):
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ 1. Legacy Format (1972-2015): 9 Digits + Letter โ
โ โ
โ Example: 941234567V โ
โ [94] [123] [4567] [V] โ
โ Birth Year | Day Code | Serial No. | Voter Status โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ 2. Modern Smart NIC (2016-present): 12 Digits โ
โ โ
โ Example: 199412345678 โ
โ [1994] [123] [4567] [8] โ
โ Century Year | Day Code | Serial No. | Checksum โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Let's break down the individual segments.
The Birth Year Encoding
-
Legacy (9-character): a 2-digit year prefix (
94โ1994) -
Modern (12-digit): the full 4-digit Gregorian year (
1994or2003), which eliminates Y2K-style ambiguity entirely ### The 3-Digit Day Code and the Gender Offset
Instead of storing month and day separately, the DRP packs both date of birth and gender into a single 3-digit ordinal day value, 001 to 866:
-
Males: ordinal day assigned directly โ
001(Jan 1) through366(Dec 31) -
Females: a constant offset of
500is added โ501(Jan 1) through866(Dec 31)
const FEMALE_OFFSET = 500;
function getGenderAndDay(rawDayCode) {
if (rawDayCode > FEMALE_OFFSET) {
return {
gender: 'Female',
dayOfYear: rawDayCode - FEMALE_OFFSET
};
}
return {
gender: 'Male',
dayOfYear: rawDayCode
};
}
This single-field design was an elegant space optimization for 1970s mainframe databases โ it recovers both gender and birth date without needing an extra database column.
The Leap Year and "Day 60" Quirk
This is where most hobbyist NIC decoders fall apart.
The DRP's encoding is based on a uniform 366-day calendar baseline, so March 1st is always treated as day 061. In non-leap years:
- Day 60 (February 29th) is simply skipped in issuance
- A day code of
061in a non-leap year still means March 1st - Converting a non-leap day number back into a calendar date means any index
>= 60needs a-1adjustment to line up with the real 365-day Gregorian calendar
function nicDayToCalendarDate(dayOfYear, year) {
const leap = (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0);
let adjustedDay = dayOfYear;
// Handle the skipped Day 60 convention on non-leap years
if (!leap && adjustedDay >= 60) {
adjustedDay -= 1;
}
const cumulativeDays = leap
? [0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366]
: [0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
let month = 1;
while (month <= 12 && adjustedDay > cumulativeDays[month]) {
month++;
}
const day = adjustedDay - cumulativeDays[month - 1];
return { month, day };
}
๐ 2. Privacy-First Architecture: Zero-Knowledge by Design
National Identity Cards are sensitive PII under privacy frameworks like Sri Lanka's Personal Data Protection Act (PDPA) No. 9 of 2022, and the GDPR.
Most "free" decoding utilities quietly send your input to /api/decode, or log it in an analytics payload. For NIC Info, I built an air-gapped client runtime instead:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Browser process memory (100% client-side) โ
โ โ
โ [User input] --> [Regex validator] --> [Parser engine] โ
โ | โ
โ [Result card] <-- [Chronology diff] <-------+ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
NO NETWORK TRANSMISSION
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ External edge (Vercel) โ
โ - Static HTML/CSS/JS delivery only โ
โ - Zero logging, zero PII ingestion โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key security implementations:
- Zero external API calls โ the decoding engine runs entirely in synchronous browser memory
-
Zero form submissions โ everything is wired to reactive DOM listeners (
input,keydown) - Hardened HTTP headers, configured at the edge:
{
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
{ "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }
]
}
โก 3. The Pure Vanilla Stack: Zero Bundlers, Zero Frameworks
It's tempting to reach for React, Next.js, or Vue even for a single-purpose utility. But for a tool people open on a patchy mobile connection, every kilobyte of JavaScript is a tax on load time.
NIC Info is built with plain semantic HTML5, vanilla CSS3, and ES6+ โ no bundler, no framework, no build step.
| Metric | Framework app (e.g. Next.js) | NIC Info (vanilla) |
|---|---|---|
| JavaScript payload | ~180 KB โ 450 KB (gzipped) | < 6 KB (unminified) |
| First Contentful Paint (FCP) | 0.8s โ 1.4s | 0.2s |
| Interaction to Next Paint (INP) | 40ms โ 120ms | < 8ms (instant) |
| Lighthouse score | 85 โ 92 | 100 / 100 |
Real-Time Chronological Age Calculation
Alongside the date of birth, the tool computes a precise, human-readable age breakdown โ years, months, and days โ using proper month-borrowing arithmetic:
function calculatePreciseAge(birthYear, birthMonth, birthDay) {
const now = new Date();
let years = now.getFullYear() - birthYear;
let months = (now.getMonth() + 1) - birthMonth;
let days = now.getDate() - birthDay;
const daysInMonth = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const isLeap = (y) => (y % 4 === 0 && y % 100 !== 0) || (y % 400 === 0);
if (days < 0) {
months--;
let prevMonth = now.getMonth(); // 0-indexed previous month
let prevYear = now.getFullYear();
if (prevMonth === 0) {
prevMonth = 12;
prevYear--;
}
const daysInPrev = (prevMonth === 2 && isLeap(prevYear)) ? 29 : daysInMonth[prevMonth];
days += daysInPrev;
}
if (months < 0) {
years--;
months += 12;
}
return { years, months, days };
}
๐จ 4. Editorial "Registry Office" Design System
Utility apps don't have to look like a generic Bootstrap table. I wanted NIC Info to feel like an authentic archival registry ledger, filtered through a modern Cupertino-style aesthetic.
Typography & Colors
-
Serif headings:
Fraunces, a variable font, brings a warm, authoritative "government ledger" feel -
Monospace elements:
IBM Plex Monokeeps numeric identification codes tabular and aligned -
Palette: deep archival navy (
#0B1118), warm parchment gold (#C9A15C), and slate borders
:root {
--bg-primary: #0F172A;
--surface-card: #1E293B;
--accent-gold: #C9A15C;
--accent-teal: #4F8B82;
--text-main: #F8FAFC;
--font-serif: 'Fraunces', Georgia, serif;
--font-mono: 'IBM Plex Mono', monospace;
}
.result-card {
background: var(--surface-card);
border: 1px solid rgba(201, 161, 92, 0.2);
box-shadow: 0 20px 40px -15px rgba(0, 0, 0, 0.5);
backdrop-filter: blur(12px);
border-radius: 16px;
}
Micro-Interactions & Usability
-
Auto-decoding as you type โ the input detects a completed 10-character (
941234567V) or 12-digit (199412345678) string and decodes it instantly, no submit button required - Interactive sample chips for one-tap testing of edge cases (leap years, voter flags, female day offsets)
- Web Share API for one-tap sharing to WhatsApp, X, or the native OS share sheet
- PWA-ready via manifest.json, installable on desktop and mobile
๐ 5. Try It Out & Contribute
The full codebase is open-source under the MIT license:
- ๐ Live app: nicinfo.vercel.app
- ๐ Format guidelines & spec: nicinfo.vercel.app/guidelines.html
pubudutharanga / NIC-Info
A free browser tool for decoding Sri Lankan National Identity Card numbers.
๐ฑ๐ฐ NIC Info
High-Performance, Privacy-First Sri Lankan National Identity Card Decoder
Instant demographic extraction, precise age breakdown, and dual-format conversion for Sri Lankan National Identity Cards.
Built with pure modern web standards, zero third-party dependencies, and an air-gapped zero-knowledge client architectureExplore Web App โข Format Guidelines โข Privacy Notice โข Operating Policy โข LLMs Specification
๐ Executive Summary
NIC Info is a client-side utility engineered to decode, parse, and validate Sri Lankan National Identity Card (NIC) numbers in real time. Designed with a bespoke "Registry Office" visual aesthetic, it provides instantaneous conversion of legacy (9-character) and modern (12-digit) identity card numbers into structured demographic profilesโextracting birth dates, exact age hierarchies, gender classifications, and administrative voter designations.
๐ Why NIC Info?
- ๐ Zero-Knowledge Privacy: 100% of data processing occurs exclusively within the local browser runtime. No identity identifiers are ever transmitted across a network, logged, or cached externally.
- โกโฆ
What's next on the roadmap
- [ ] Multilingual localization (Sinhala เทเทเถเทเถฝ, Tamil เฎคเฎฎเฎฟเฎดเฏ)
- [ ] Educational CSV batch-verification mode for administrative workloads (still 100% client-side)
- [ ] Printable / downloadable demographic ID cards in vector SVG/PDF format
๐ก Key Takeaway for Developers
You don't need an 80MB node_modules folder or a distributed backend cluster to build something valuable and fast.
Understand your domain deeply, respect user privacy by default, and write clean, standards-compliant code โ and you can ship something that boots in sub-milliseconds and never puts your users' data at risk.
Did you find this breakdown helpful? I'd love to hear your own edge cases or decoding war stories in the comments below. ๐
Top comments (0)