Skip to content

Commit

Permalink
Merge pull request #103 from koblas/se_personnummer
Browse files Browse the repository at this point in the history
feat: se personnnumer formatting normalizes data
  • Loading branch information
koblas committed Aug 30, 2023
2 parents a66ba27 + 34b1c1f commit 1baabcf
Show file tree
Hide file tree
Showing 28 changed files with 190 additions and 87 deletions.
2 changes: 1 addition & 1 deletion src/at/vnr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const impl: Validator = {

const [front, check, dob] = strings.splitAt(value, 3, 4);

if (!isValidDateCompactDDMMYY(dob)) {
if (!isValidDateCompactDDMMYY(dob, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

Expand Down
4 changes: 2 additions & 2 deletions src/ba/jmbg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import * as exceptions from '../exceptions';
import { isValidDateCompactYYYYMMDD, strings } from '../util';
import { isValidDate, strings } from '../util';
import { Validator, ValidateReturn } from '../types';
import { weightedSum } from '../util/checksum';

Expand Down Expand Up @@ -55,7 +55,7 @@ const impl: Validator = {
const [dd, mm, yyy] = strings.splitAt(value, 2, 4, 7);
const yyyy = `${parseInt(yyy, 10) < 800 ? '2' : '1'}${yyy}`;

if (!isValidDateCompactYYYYMMDD(`${yyyy}${mm}${dd}`)) {
if (!isValidDate(yyyy, mm, dd, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

Expand Down
2 changes: 1 addition & 1 deletion src/cn/ric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const impl: Validator = {
return { isValid: false, error: new exceptions.InvalidFormat() };
}

if (!isValidDateCompactYYYYMMDD(front.substr(6, 8))) {
if (!isValidDateCompactYYYYMMDD(front.substring(6, 14), true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

Expand Down
4 changes: 2 additions & 2 deletions src/cu/ni.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import * as exceptions from '../exceptions';
import { isValidDateCompactYYYYMMDD, strings } from '../util';
import { isValidDate, strings } from '../util';
import { Validator, ValidateReturn } from '../types';

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
Expand Down Expand Up @@ -57,7 +57,7 @@ const impl: Validator = {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

if (!isValidDateCompactYYYYMMDD(`${year}${mm}${dd}`)) {
if (!isValidDate(year, mm, dd, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

Expand Down
6 changes: 2 additions & 4 deletions src/cz/rc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/

import * as exceptions from '../exceptions';
import { isValidDateCompactYYYYMMDD, strings } from '../util';
import { isValidDate, strings } from '../util';
import { Validator, ValidateReturn } from '../types';

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
Expand Down Expand Up @@ -66,9 +66,7 @@ const impl: Validator = {
} else if (year < 1954) {
year += 100;
}
if (
!isValidDateCompactYYYYMMDD(`${year}${String(mon).padStart(2, '0')}${dd}`)
) {
if (!isValidDate(String(year), String(mon), dd, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

Expand Down
2 changes: 1 addition & 1 deletion src/de/pwnr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function validateNew(value: string): ValidateReturn {
checksum,
] = strings.splitAt(value, 9, 10, 16, 17, 23, 24, 25);

if (!isValidDateCompactYYMMDD(birth)) {
if (!isValidDateCompactYYMMDD(birth, true)) {
return {
isValid: false,
error: new exceptions.InvalidComponent('birthdate'),
Expand Down
22 changes: 10 additions & 12 deletions src/dk/cpr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
*/

import * as exceptions from '../exceptions';
import { strings } from '../util';
import { isValidDate, buildDate, validBirthdate, strings } from '../util';
import { Validator, ValidateReturn } from '../types';

export function getBirthDate(value: string): Date {
const [dob] = strings.splitAt(value, 6);
// eslint-disable-next-line prefer-const
let [day, month, year] = strings.splitAt(dob, 2, 4).map(v => Number(v));
const [day, month, yearStr] = strings.splitAt(dob, 2, 4);

let year = parseInt(yearStr);

if ('5678'.includes(value[6]) && year >= 58) {
year += 1800;
Expand All @@ -38,19 +39,16 @@ export function getBirthDate(value: string): Date {
} else {
year += 2000;
}
const date = new Date(year, month - 1, day);
if (
Number.isNaN(date.getTime()) ||
[year, String(month).padStart(2, '0'), String(day).padStart(2, '0')].join(
'-',
) !== date.toISOString().substr(0, 10)
) {

const d = buildDate(String(year), month, day);

if (d === null || !isValidDate(String(year), month, day)) {
throw new exceptions.InvalidComponent(
'The number does not contain valid birth date information.',
);
}

return date;
return d;
}

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
Expand Down Expand Up @@ -97,7 +95,7 @@ const impl: Validator = {

try {
const date = getBirthDate(value);
if (date.getTime() > new Date().getTime()) {
if (!validBirthdate(date)) {
return {
isValid: false,
error: new exceptions.InvalidComponent(
Expand Down
2 changes: 1 addition & 1 deletion src/ee/ik.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function ikCheckDate(value: string): boolean {
return false;
}

return isValidDateCompactYYYYMMDD(`${century}${value.substr(1, 6)}`);
return isValidDateCompactYYYYMMDD(`${century}${value.substr(1, 6)}`, true);
}

const impl: Validator = {
Expand Down
2 changes: 1 addition & 1 deletion src/fi/hetu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('fi/hetu', () => {
expect(result.isValid && result.compact).toEqual('131052-308T');
});

test.each(['131052B308T', '131052X308T', '131052-308T'])(
test.each(['131022B3082', '131052X308T', '131052-308T'])(
'validate:%s',
value => {
const result = validate(value);
Expand Down
5 changes: 2 additions & 3 deletions src/fi/hetu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/

import * as exceptions from '../exceptions';
import { isValidDateCompactYYYYMMDD, strings } from '../util';
import { isValidDate, strings } from '../util';
import { Validator, ValidateReturn } from '../types';

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
Expand Down Expand Up @@ -86,8 +86,7 @@ const impl: Validator = {
7,
10,
);
const dstr = `${CENTURY[century]}${yy}${mm}${dd}`;
if (!isValidDateCompactYYYYMMDD(dstr)) {
if (!isValidDate(`${CENTURY[century]}${yy}`, mm, dd, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

Expand Down
4 changes: 2 additions & 2 deletions src/gr/amka.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*/

import * as exceptions from '../exceptions';
import { isValidDateCompactYYMMDD, strings } from '../util';
import { isValidDate, strings } from '../util';
import { Validator, ValidateReturn } from '../types';
import { luhnChecksumValidate } from '../util/checksum';

Expand Down Expand Up @@ -56,7 +56,7 @@ const impl: Validator = {

const [dd, mm, yy] = strings.splitAt(value, 2, 4, 6);

if (!isValidDateCompactYYMMDD(`${yy}${mm}${dd}`)) {
if (!isValidDate(yy, mm, dd)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (!luhnChecksumValidate(value)) {
Expand Down
4 changes: 2 additions & 2 deletions src/is/kennitala.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
*/

import * as exceptions from '../exceptions';
import { isValidDateCompactYYYYMMDD, strings, weightedSum } from '../util';
import { isValidDate, strings, weightedSum } from '../util';
import { Validator, ValidateReturn } from '../types';

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
Expand Down Expand Up @@ -54,7 +54,7 @@ const impl: Validator = {
const day = dayValue > 40 ? String(dayValue - 40).padStart(2, '0') : dd;
const year = centry === '9' ? `19${yy}` : `20${yy}`;

if (!isValidDateCompactYYYYMMDD(`${year}${mm}${day}`)) {
if (!isValidDate(year, mm, day)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

Expand Down
2 changes: 1 addition & 1 deletion src/kr/rrn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const impl: Validator = {

const [dob, century, place, , check] = strings.splitAt(value, 6, 7, 9, 12);

if (!isValidDateCompactYYYYMMDD(`${centuryPrefix[century]}${dob}`)) {
if (!isValidDateCompactYYYYMMDD(`${centuryPrefix[century]}${dob}`, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (parseInt(place, 10) > 96) {
Expand Down
8 changes: 2 additions & 6 deletions src/lv/pvn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
*/

import * as exceptions from '../exceptions';
import { isValidDateCompactYYYYMMDD, strings } from '../util';
import { isValidDate, strings } from '../util';
import { Validator, ValidateReturn } from '../types';
import { weightedSum } from '../util/checksum';

Expand Down Expand Up @@ -62,11 +62,7 @@ const impl: Validator = {
if (isIndividual) {
const [dd, mm, yy, century] = strings.splitAt(value, 2, 4, 6, 7);

if (
!isValidDateCompactYYYYMMDD(
`${18 + parseInt(century, 10)}${yy}${mm}${dd}`,
)
) {
if (!isValidDate(`${18 + parseInt(century, 10)}${yy}`, mm, dd, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

Expand Down
2 changes: 1 addition & 1 deletion src/mx/curp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('mx/curp', () => {
it('getBirthDate:BOXW310820HNERXN09', () => {
const result = getBirthDate('BOXW310820HNERXN09');

expect(result.toISOString().substr(0, 10)).toEqual('1931-08-20');
expect(result?.toISOString().substr(0, 10)).toEqual('1931-08-20');
});

it('getGender:BOXW310820HNERXN09', () => {
Expand Down
28 changes: 15 additions & 13 deletions src/mx/curp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@
*/

import * as exceptions from '../exceptions';
import { isValidDateCompactYYMMDD, strings } from '../util';
import {
buildDate,
isValidDateCompactYYMMDD,
strings,
validBirthdate,
} from '../util';
import { Validator, ValidateReturn } from '../types';

function clean(input: string): ReturnType<typeof strings.cleanUnicode> {
Expand Down Expand Up @@ -184,7 +189,7 @@ const impl: Validator = {
if (!/^[A-Z]{4}[0-9]{6}[A-Z]{6}[0-9A-Z][0-9]$/.test(value)) {
return { isValid: false, error: new exceptions.InvalidFormat() };
}
if (!isValidDateCompactYYMMDD(value.substr(4, 6))) {
if (!isValidDateCompactYYMMDD(value.substr(4, 6), true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (nameBlacklist.has(value.substr(0, 4))) {
Expand Down Expand Up @@ -219,17 +224,12 @@ const impl: Validator = {
},
};

function getBirthDateImpl(value: string) {
const parts = strings.splitAt(value, 4, 6, 8);
function getBirthDateImpl(value: string): Date | null {
const [, year, mm, dd] = strings.splitAt(value, 4, 6, 8);

const yyN = parseInt(parts[1], 10);
const mmN = parseInt(parts[2], 10) - 1;
const ddN = parseInt(parts[3], 10);
const century = !Number.isNaN(parseInt(value[16], 10)) ? '19' : '20';

if (!Number.isNaN(parseInt(value[16], 10))) {
return new Date(yyN + 1900, mmN, ddN);
}
return new Date(yyN + 2000, mmN, ddN);
return buildDate(`${century}${year}`, mm, dd);
}

///
Expand All @@ -249,10 +249,12 @@ export function getGender(input: string): 'M' | 'F' | 'X' {
return 'F';
}

export function getBirthDate(input: string): Date {
export function getBirthDate(input: string): Date | null {
const value = impl.compact(input);

return getBirthDateImpl(value);
const date = getBirthDateImpl(value);

return validBirthdate(date) ? date : null;
}

export const { name, localName, abbreviation, validate, format, compact } =
Expand Down
2 changes: 1 addition & 1 deletion src/mx/rfc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const impl: Validator = {
if (nameBlacklist.has(value.substr(0, 4))) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (!isValidDateCompactYYMMDD(value.substr(4, 6))) {
if (!isValidDateCompactYYMMDD(value.substr(4, 6), true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
} else if (value.length === 12) {
Expand Down
2 changes: 1 addition & 1 deletion src/my/nric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ const impl: Validator = {

const [bdate, place] = strings.splitAt(value, 6, 8);

if (!isValidDateCompactYYMMDD(bdate)) {
if (!isValidDateCompactYYMMDD(bdate, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (UNASSIGNED.includes(place)) {
Expand Down
2 changes: 1 addition & 1 deletion src/no/fodselsnummer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ function checkBirthdate(value: string) {
return false;
}

return isValidDate(String(yy), String(mm), String(dd));
return isValidDate(String(yy), String(mm), String(dd), true);
}

const impl: Validator = {
Expand Down
2 changes: 1 addition & 1 deletion src/pl/pesel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ const impl: Validator = {
century = '19';
}

if (!isValidDate(`${century}${yy}`, String(month % 20), dd)) {
if (!isValidDate(`${century}${yy}`, String(month % 20), dd, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}

Expand Down
2 changes: 1 addition & 1 deletion src/ro/cnp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ const impl: Validator = {

const [first, dvalue, county] = strings.splitAt(value, 1, 7, 9);

if (!isValidDateCompactYYYYMMDD(`${century[first]}${dvalue}`)) {
if (!isValidDateCompactYYYYMMDD(`${century[first]}${dvalue}`, true)) {
return { isValid: false, error: new exceptions.InvalidComponent() };
}
if (!VALID_COUNTIES.includes(county)) {
Expand Down
16 changes: 12 additions & 4 deletions src/se/personnummer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@ import { validate, format } from './personnummer';
import { InvalidLength, InvalidChecksum } from '../exceptions';

describe('se/personnummer', () => {
it('format:8803200016', () => {
const result = format('8803200016');

expect(result).toEqual('880320-0016');
test.each([
['8803200017', '880320-0017'], // no hyphen assume young person
['880320-0017', '880320-0017'], // standard format
['880320+0017', '880320+0017'], // standard format, person over 100
['19200901-0000', '200901+0000'], // 4digit year, hypen that's useless person over 100
['192009010000', '200901+0000'], // 4digit year, no hyphen, person over 100
['20200901-0000', '200901-0000'], // 4digit year, hyphen
['202009010000', '200901-0000'], // 4digit year, no hyphen
])('format:%s', (given, want) => {
const result = format(given);

expect(result).toEqual(want);
});

it('validate:880320-0016', () => {
Expand Down
Loading

0 comments on commit 1baabcf

Please sign in to comment.