Skip to content

Commit d907fb1

Browse files
committed
personalidenitynumbervaldiation
1 parent 716fb12 commit d907fb1

File tree

4 files changed

+89
-1
lines changed

4 files changed

+89
-1
lines changed

packages/validation/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,8 @@
2929
],
3030
"scripts": {
3131
"build": "bunchee"
32+
},
33+
"devDependencies": {
34+
"nav-faker": "3.2.4"
3235
}
3336
}

packages/validation/src/no.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,56 @@ export function validateObosMembershipNumber(
103103

104104
return /^\d{7}$/.test(value);
105105
}
106+
107+
type PersonalIdentityNumberOptions = ValidatorOptions;
108+
109+
/**
110+
* Validates that the input value is a Norwegian personal identity number.
111+
*
112+
* Supports both Fødselsnummer, D-numbers and H-numbers.
113+
* @example
114+
* ```
115+
* validatePersonalIdentityNumber('0000000') // => true
116+
* ```
117+
*/
118+
export function validatePersonalIdentityNumber(
119+
value: string,
120+
options: PersonalIdentityNumberOptions = {},
121+
): boolean {
122+
if (options.allowFormatting) {
123+
// biome-ignore lint/style/noParameterAssign:
124+
value = stripFormatting(value);
125+
}
126+
127+
// Fødselsnummer and d numbers have two control digits.
128+
// where the first one is calculated using the first 10 digits.
129+
const valueForControlDigit1 = value.slice(0, -1);
130+
const controlDigit1 = mod11(
131+
valueForControlDigit1,
132+
[3, 7, 6, 1, 8, 9, 4, 5, 2],
133+
);
134+
const controlDigit2 = mod11(value, [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]);
135+
136+
if (!controlDigit1 && controlDigit2) {
137+
return false;
138+
}
139+
140+
let day = Number(value.substring(0, 2));
141+
let month = Number(value.substring(2, 4));
142+
const year = Number(value.substring(4, 6));
143+
144+
switch (true) {
145+
// d number
146+
case value[0] >= 4:
147+
day = day - 40;
148+
break;
149+
// h number
150+
case value[2] >= 4:
151+
month = month - 40;
152+
break;
153+
}
154+
155+
const date = new Date(Date.UTC(year, month - 1, day));
156+
157+
return date && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
158+
}

packages/validation/src/validation.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import navfaker from 'nav-faker/dist/index';
12
import { describe, expect, test } from 'vitest';
23
import * as no from './no';
34
import * as se from './se';
@@ -58,6 +59,21 @@ describe('no', () => {
5859
])('validateObosMembershipNumber(%s) -> %s', (input, expected, options) => {
5960
expect(no.validateObosMembershipNumber(input, options)).toBe(expected);
6061
});
62+
63+
test('validatePersonalIdentityNumber()', () => {
64+
for (let i = 0; i < 1000; ++i) {
65+
expect(
66+
no.validatePersonalIdentityNumber(
67+
navfaker.personIdentifikator.fødselsnummer(),
68+
),
69+
).toBe(true);
70+
expect(
71+
no.validatePersonalIdentityNumber(
72+
navfaker.personIdentifikator.dnummer(),
73+
),
74+
).toBe(true);
75+
}
76+
});
6177
});
6278

6379
describe('se', () => {

pnpm-lock.yaml

Lines changed: 17 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)