UNPKG

stdnum

Version:
159 lines (138 loc) 3.68 kB
/** * CNP (Cod Numeric Personal, Romanian Numerical Personal Code). * * The CNP is a 13 digit number that includes information on the person's * gender, birth date and country zone. * * Source: * https://ro.wikipedia.org/wiki/Cod_numeric_personal * * PERSON */ import * as exceptions from '../exceptions'; import { isValidDateCompactYYYYMMDD, strings, weightedSum } from '../util'; import { Validator, ValidateReturn } from '../types'; function clean(input: string): ReturnType<typeof strings.cleanUnicode> { return strings.cleanUnicode(input, ' -'); } const century: Record<string, string> = { '0': '19', // shouldn't happen, '1': '19', '2': '19', '3': '18', '4': '18', '5': '20', '6': '20', '7': '19', '8': '19', '9': '19', }; // The Romanian counties const COUNTIES: Record<string, string> = { '01': 'Alba', '02': 'Arad', '03': 'Arges', '04': 'Bacau', '05': 'Bihor', '06': 'Bistrita-Nasaud', '07': 'Botosani', '08': 'Brasov', '09': 'Braila', '10': 'Buzau', '11': 'Caras-Severin', '12': 'Cluj', '13': 'Constanta', '14': 'Covasna', '15': 'Dambovita', '16': 'Dolj', '17': 'Galati', '18': 'Gorj', '19': 'Harghita', '20': 'Hunedoara', '21': 'Ialomita', '22': 'Iasi', '23': 'Ilfov', '24': 'Maramures', '25': 'Mehedinti', '26': 'Mures', '27': 'Neamt', '28': 'Olt', '29': 'Prahova', '30': 'Satu Mare', '31': 'Salaj', '32': 'Sibiu', '33': 'Suceava', '34': 'Teleorman', '35': 'Timis', '36': 'Tulcea', '37': 'Vaslui', '38': 'Valcea', '39': 'Vrancea', '40': 'Bucuresti', '41': 'Bucuresti - Sector 1', '42': 'Bucuresti - Sector 2', '43': 'Bucuresti - Sector 3', '44': 'Bucuresti - Sector 4', '45': 'Bucuresti - Sector 5', '46': 'Bucuresti - Sector 6', '47': 'Bucuresti - Sector 7 (desfiintat)', '48': 'Bucuresti - Sector 8 (desfiintat)', '51': 'Calarasi', '52': 'Giurgiu', }; const VALID_COUNTIES = Object.keys(COUNTIES); const impl: Validator = { name: 'Romanian Numerical Personal Code', localName: 'Cod Numeric Personal', abbreviation: 'CNP', compact(input: string): string { const [value, err] = clean(input); if (err) { throw err; } return value; }, format(input: string): string { const [value] = clean(input); return value; }, validate(input: string): ValidateReturn { const [value, error] = clean(input); if (error) { return { isValid: false, error }; } if (value.length !== 13) { return { isValid: false, error: new exceptions.InvalidLength() }; } if (!strings.isdigits(value)) { return { isValid: false, error: new exceptions.InvalidFormat() }; } if (value[0] === '0') { return { isValid: false, error: new exceptions.InvalidComponent() }; } const [first, dvalue, county] = strings.splitAt(value, 1, 7, 9); if (!isValidDateCompactYYYYMMDD(`${century[first]}${dvalue}`, true)) { return { isValid: false, error: new exceptions.InvalidComponent() }; } if (!VALID_COUNTIES.includes(county)) { return { isValid: false, error: new exceptions.InvalidComponent() }; } const [front, check] = strings.splitAt(value, -1); const sum = weightedSum(front, { weights: [2, 7, 9, 1, 4, 6, 3, 5, 8, 2, 7, 9], modulus: 11, }); const digit = sum === 10 ? '1' : String(sum); if (check !== digit) { return { isValid: false, error: new exceptions.InvalidChecksum() }; } return { isValid: true, compact: value, isIndividual: true, isCompany: false, }; }, }; export const { name, localName, abbreviation, validate, format, compact } = impl;