From ce8834c3b09fb1d11e1711ceaa054bfc436bfac1 Mon Sep 17 00:00:00 2001 From: Yarchik Date: Thu, 18 Jun 2026 15:39:45 +0100 Subject: [PATCH 1/2] fix(bg/egn): validate the embedded birth date The first six digits of a Bulgarian EGN are a birth date whose month encodes the century (41-52 -> 2000s, 21-32 -> 1800s, 01-12 -> 1900s), but validate() only checked the length and the check digit. A number with an impossible date and a correct check digit was accepted: validate('2992070971') // month 92 -> reported valid python-stdnum rejects these as InvalidComponent. Decode the century from the month and validate the date, matching the reference. Verified against python-stdnum: 0 disagreements over 50k random 10-digit inputs (was: JS accepted invalid-date numbers python rejects). --- src/bg/egn.spec.ts | 21 ++++++++++++++++++++- src/bg/egn.ts | 18 +++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/bg/egn.spec.ts b/src/bg/egn.spec.ts index 75c1bcab..215ecc8f 100644 --- a/src/bg/egn.spec.ts +++ b/src/bg/egn.spec.ts @@ -1,5 +1,9 @@ import { validate, format } from './egn'; -import { InvalidLength, InvalidChecksum } from '../exceptions'; +import { + InvalidLength, + InvalidChecksum, + InvalidComponent, +} from '../exceptions'; describe('bg/egn', () => { it('format:752316 926 3', () => { @@ -25,4 +29,19 @@ describe('bg/egn', () => { expect(result.error).toBeInstanceOf(InvalidChecksum); }); + + // The first six digits are a birth date whose month encodes the century + // (41-52 -> 2000s, 21-32 -> 1800s); an impossible date must be rejected even + // when the check digit is correct. + it('validate:2992070971 (impossible month, valid check digit)', () => { + const result = validate('2992070971'); + + expect(result.error).toBeInstanceOf(InvalidComponent); + }); + + it('validate:8019010008 (impossible month)', () => { + const result = validate('8019010008'); + + expect(result.error).toBeInstanceOf(InvalidComponent); + }); }); diff --git a/src/bg/egn.ts b/src/bg/egn.ts index 37ee996d..937e9e4d 100644 --- a/src/bg/egn.ts +++ b/src/bg/egn.ts @@ -10,7 +10,7 @@ */ import * as exceptions from '../exceptions'; -import { strings } from '../util'; +import { isValidDate, strings } from '../util'; import { Validator, ValidateReturn } from '../types'; import { weightedSum } from '../util/checksum'; @@ -51,6 +51,22 @@ const impl: Validator = { return { isValid: false, error: new exceptions.InvalidFormat() }; } + // The first six digits are the birth date. The month encodes the century: + // 41-52 -> 2000s, 21-32 -> 1800s, 01-12 -> 1900s. + const [yy, mm, dd] = strings.splitAt(value, 2, 4, 6); + let year = parseInt(yy, 10) + 1900; + let month = parseInt(mm, 10); + if (month > 40) { + year += 100; + month -= 40; + } else if (month > 20) { + year -= 100; + month -= 20; + } + if (!isValidDate(String(year), String(month), dd)) { + return { isValid: false, error: new exceptions.InvalidComponent() }; + } + const [front, check] = strings.splitAt(value, -1); const sum = weightedSum(front, { From b4dc0750bf0102792174b3958b5092503e502d40 Mon Sep 17 00:00:00 2001 From: Yarchik Date: Thu, 18 Jun 2026 18:41:58 +0100 Subject: [PATCH 2/2] refactor(bg/egn): assign the century directly per month range Set each branch's century explicitly (2000s/1800s/1900s) instead of starting at 1900 and adjusting by +/-100, so the code mirrors the documented mapping and the reference implementation. Behaviour is unchanged. --- src/bg/egn.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bg/egn.ts b/src/bg/egn.ts index 937e9e4d..0777f3ea 100644 --- a/src/bg/egn.ts +++ b/src/bg/egn.ts @@ -54,14 +54,16 @@ const impl: Validator = { // The first six digits are the birth date. The month encodes the century: // 41-52 -> 2000s, 21-32 -> 1800s, 01-12 -> 1900s. const [yy, mm, dd] = strings.splitAt(value, 2, 4, 6); - let year = parseInt(yy, 10) + 1900; + let year = parseInt(yy, 10); let month = parseInt(mm, 10); if (month > 40) { - year += 100; + year += 2000; month -= 40; } else if (month > 20) { - year -= 100; + year += 1800; month -= 20; + } else { + year += 1900; } if (!isValidDate(String(year), String(month), dd)) { return { isValid: false, error: new exceptions.InvalidComponent() };