Skip to content

Commit 4ae26d2

Browse files
authored
Merge branch 'main' into feature/isvalidswissibannumber
2 parents 42ce69d + 18d0936 commit 4ae26d2

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- `isValidSwissIbanNumber` string utility function
13+
- `isValidSwissSocialSecurityNumber` string utility function
1314

1415
## [2.0.0] - 2025-07-29
1516

src/lib/string.spec.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, isValidSwissIbanNumber } from "./string";
1+
import { isNullOrEmpty, isNullOrWhitespace, capitalize, uncapitalize, truncate, isValidSwissIbanNumber ,isValidSwissSocialSecurityNumber } from "./string";
22

33
describe("string tests", () => {
44
test.each([
@@ -134,4 +134,19 @@ describe("string tests", () => {
134134
])("check if this swiss IBAN is valid or not", (unformattedIbanNumber, expected) => {
135135
expect(isValidSwissIbanNumber(unformattedIbanNumber)).toBe(expected);
136136
});
137-
});
137+
138+
test.each([
139+
[null as unknown as string, false],
140+
[undefined as unknown as string, false],
141+
["7561234567891", false],
142+
["7569217076985", true],
143+
["756.92170769.85", false],
144+
["756.9217.0769.85", true],
145+
["756..9217.0769.85", false],
146+
["756.1234.5678.91", false],
147+
["test756.9217.0769.85", false],
148+
["7.56..9217...0769.85", false],
149+
])("check if the social insurance number is valid or not", (ahvNumber, expected) => {
150+
expect(isValidSwissSocialSecurityNumber(ahvNumber)).toBe(expected);
151+
});
152+
});

src/lib/string.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,66 @@ export function isValidSwissIbanNumber(ibanNumber: string): boolean {
109109
// 8. IBAN is valid only if the remainder equals 1
110110
return restOfCalculation === 1;
111111
}
112+
113+
/**
114+
* Validation of social insurance number with checking the checksum
115+
* Validation according to https://www.sozialversicherungsnummer.ch/aufbau-neu.htm
116+
* @param socialInsuranceNumber The social insurance number to check
117+
* Must be in one of the following formats:
118+
* - "756.XXXX.XXXX.XX" with dots as separators
119+
* - "756XXXXXXXXXX" with digits only
120+
* @returns The result if the social insurance number is valid or not
121+
*/
122+
export function isValidSwissSocialSecurityNumber(socialInsuranceNumber: string): boolean {
123+
// 1. Check if input is empty or only whitespace
124+
if (isNullOrWhitespace(socialInsuranceNumber)) {
125+
return false;
126+
}
127+
128+
/**
129+
* 2. Check if input matches accepted formats:
130+
* - With dots: 756.XXXX.XXXX.XX
131+
* - Without dots: 756XXXXXXXXXX
132+
*/
133+
const socialInsuranceNumberWithDots = new RegExp(/^756\.\d{4}\.\d{4}\.\d{2}$/);
134+
const socialInsuranceNumberWithoutDots = new RegExp(/^756\d{10}$/);
135+
136+
if (!socialInsuranceNumberWithDots.test(socialInsuranceNumber) && !socialInsuranceNumberWithoutDots.test(socialInsuranceNumber)) {
137+
return false;
138+
}
139+
140+
// 3. Remove all dots → get a string of 13 digits
141+
const compactNumber = socialInsuranceNumber.replaceAll(".", "");
142+
143+
/**
144+
* 4. Separate digits for checksum calculation
145+
* - first 12 digits: used to calculate checksum
146+
* - last digit: actual check digit
147+
*/
148+
const digits = compactNumber.slice(0, -1);
149+
const reversedDigits = [...digits].reverse().join("");
150+
const reversedDigitsArray = [...reversedDigits];
151+
152+
/*
153+
* 5. Calculate weighted sum for checksum
154+
* - Even positions (after reversing) ×3
155+
* - Odd positions ×1
156+
*/
157+
let sum = 0;
158+
for (const [i, element] of reversedDigitsArray.entries()) {
159+
sum += i % 2 === 0 ? Number(element) * 3 : Number(element) * 1;
160+
}
161+
162+
/*
163+
* 6. Calculate expected check digit
164+
* - Check digit = value to reach next multiple of 10
165+
*/
166+
const checksum = (10 - (sum % 10)) % 10;
167+
const checknumber = Number.parseInt(compactNumber.slice(-1));
168+
169+
/*
170+
* 7. Compare calculated check digit with actual last digit
171+
* - If equal → valid AHV number
172+
*/
173+
return checksum === checknumber;
174+
}

0 commit comments

Comments
 (0)