From cd1dbbad8d2d29b8016af3ab507e1c8f6a96ae18 Mon Sep 17 00:00:00 2001 From: Alexander Bjerkan Date: Sat, 25 Jan 2025 19:54:33 +0100 Subject: [PATCH 1/3] formatphonenumber --- packages/format/src/no.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/format/src/no.ts b/packages/format/src/no.ts index bd59b6f..cdb4077 100644 --- a/packages/format/src/no.ts +++ b/packages/format/src/no.ts @@ -1,5 +1,25 @@ import { replaceIfMatch } from './utils'; +// See https://sprakradet.no/godt-og-korrekt-sprak/rettskriving-og-grammatikk/tall-tid-dato/ +const REGULAR_PHONE_NUMBER_FORMAT = /^(\d{2})(\d{2})(\d{2})(\d{2})$/; +const EIGHT_HUNDRED_SERIES_PHONE_NUMBER_FORMAT = /^(\d{3})(\d{2})(\d{3})$/; + +/** + * Format a phone number + * @example + * ``` + * formatPhoneNumber('00000000') // => '00 00 00 00' + * formatPhoneNumber('80000000') // => '800 00 000' + * ``` + */ +export function formatPhoneNumber(number: string): string { + if (number.startsWith('8')) { + return number.replace(EIGHT_HUNDRED_SERIES_PHONE_NUMBER_FORMAT, '$1 $2 $3'); + } + + return number.replace(DEFAULT_PHONE_NUMBER_FORMAT, '$1 $2 $3 $4'); +} + const ORG_NUMBER_FORMAT = /^(\d{3})(\d{3})(\d{3})$/; /** From 9044327c29b2baa975cbcc3e45b4f4ca32bf1fa0 Mon Sep 17 00:00:00 2001 From: Alexander Bjerkan Date: Sat, 25 Jan 2025 22:02:07 +0100 Subject: [PATCH 2/3] formatPhoneNumber --- packages/format/README.md | 1 + packages/format/src/format.test.ts | 34 +++++++++++++++++++ packages/format/src/no.ts | 21 +++++++++--- packages/format/src/se.ts | 54 +++++++++++++++++++++++++++++- packages/format/src/utils.ts | 10 ++++-- 5 files changed, 111 insertions(+), 9 deletions(-) diff --git a/packages/format/README.md b/packages/format/README.md index e7f2cb4..aefd172 100644 --- a/packages/format/README.md +++ b/packages/format/README.md @@ -36,5 +36,6 @@ formatOrganizationNumber('0000000000') // => '000000-0000' ## Methods +* formatPhoneNumber * formatOrganizationNumber * formatObosMembershipNumber diff --git a/packages/format/src/format.test.ts b/packages/format/src/format.test.ts index b470766..c5650d8 100644 --- a/packages/format/src/format.test.ts +++ b/packages/format/src/format.test.ts @@ -2,13 +2,22 @@ import { describe, expect, test } from 'vitest'; import { formatObosMembershipNumber as formatObosMembershipNumberNo, formatOrganizationNumber as formatOrganizationNumberNo, + formatPhoneNumber as formatPhoneNumberNo, } from './no'; import { formatObosMembershipNumber as formatObosMembershipNumberSe, formatOrganizationNumber as formatOrganizationNumberSe, + formatPhoneNumber as formatPhoneNumberSe, } from './se'; describe('no', () => { + test.each([ + ['22865500', '22 86 55 00'], + ['80000000', '800 00 000'], + ])('formatPhoneNumber(%s) -> %s', (input, expected) => { + expect(formatPhoneNumberNo(input)).toBe(expected); + }); + test.each([ ['000000000', '000 000 000'], ['000 000 000', '000 000 000'], @@ -20,6 +29,31 @@ describe('no', () => { }); describe('se', () => { + test.each([ + // mobile phone numbers + ['0701234567', '070-123 45 67'], + ['070 12 345 67', '070-123 45 67'], + // 2 digit area code + ['0812345', '08-123 45'], + ['08123456', '08-12 34 56'], + ['081234567', '08-123 45 67'], + ['0812345678', '08-123 456 78'], + // 3 digit area code + ['03112345', '031-123 45'], + ['031123456', '031-12 34 56'], + ['0311234567', '031-123 45 67'], + ['03112345678', '031-123 456 78'], + // 4 digit area code + ['030312345', '0303-123 45'], + ['0303123456', '0303-12 34 56'], + ['03031234567', '0303-123 45 67'], + ['030312345678', '0303-123 456 78'], + // invalid, too long a number + ['0303123456789', '0303123456789'], + ])('formatPhoneNumber(%s) -> %s', (input, expected) => { + expect(formatPhoneNumberSe(input)).toBe(expected); + }); + test.each([ ['0000000000', '000000-0000'], ['000000-0000', '000000-0000'], diff --git a/packages/format/src/no.ts b/packages/format/src/no.ts index cdb4077..b53be2d 100644 --- a/packages/format/src/no.ts +++ b/packages/format/src/no.ts @@ -1,5 +1,7 @@ import { replaceIfMatch } from './utils'; +// Regular phone number format is: 00 00 00 00 +// if the number starts with 8, it's an 800-series number, with the format: 800 00 000 // See https://sprakradet.no/godt-og-korrekt-sprak/rettskriving-og-grammatikk/tall-tid-dato/ const REGULAR_PHONE_NUMBER_FORMAT = /^(\d{2})(\d{2})(\d{2})(\d{2})$/; const EIGHT_HUNDRED_SERIES_PHONE_NUMBER_FORMAT = /^(\d{3})(\d{2})(\d{3})$/; @@ -12,12 +14,21 @@ const EIGHT_HUNDRED_SERIES_PHONE_NUMBER_FORMAT = /^(\d{3})(\d{2})(\d{3})$/; * formatPhoneNumber('80000000') // => '800 00 000' * ``` */ -export function formatPhoneNumber(number: string): string { - if (number.startsWith('8')) { - return number.replace(EIGHT_HUNDRED_SERIES_PHONE_NUMBER_FORMAT, '$1 $2 $3'); - } +export function formatPhoneNumber(input: string): string { + const number = replaceIfMatch( + input, + REGULAR_PHONE_NUMBER_FORMAT, + '$1 $2 $3 $4', + ); - return number.replace(DEFAULT_PHONE_NUMBER_FORMAT, '$1 $2 $3 $4'); + // if the number starts with 8, it's an 800-series number, so we'll format it differently + return number.startsWith('8') + ? replaceIfMatch( + number, + EIGHT_HUNDRED_SERIES_PHONE_NUMBER_FORMAT, + '$1 $2 $3', + ) + : number; } const ORG_NUMBER_FORMAT = /^(\d{3})(\d{3})(\d{3})$/; diff --git a/packages/format/src/se.ts b/packages/format/src/se.ts index be1f380..295c07a 100644 --- a/packages/format/src/se.ts +++ b/packages/format/src/se.ts @@ -1,4 +1,56 @@ -import { replaceIfMatch } from './utils'; +import { cleanInput, replaceIfMatch } from './utils'; + +const MOBILE_PHONE_NUMBER_FORMAT = /^(07[02369]{1})(\d{3})(\d{2})(\d{2})$/; +// subscriber numbers, without the area codes, can be 5, 6, 7 or 8 digits long +const SUBSCRIBER_NUMBER_FORMATS = { + 5: /^(\d{3})(\d{2})$/, + 6: /^(\d{2})(\d{2})(\d{2})$/, + 7: /^(\d{3})(\d{2})(\d{2})$/, + 8: /^(\d{3})(\d{3})(\d{2})$/, +}; +// SE numbers have area codes of 2, 3 or 4 digits +const TWO_DIGIT_AREA_CODE = /^08/; +const THREE_DIGIT_AREA_CODE = + /^0(11|13|16|18|19|21|23|26|31|33|35|36|40|42|44|46|54|60|63|90)/; + +/** + * Format a phone number + * @example + * ``` + * formatPhoneNumber('07012345678') // => '070-123 45 678' + * formatPhoneNumber('0812345') // => '08-123 45' + * formatPhoneNumber('0311234567') // => '031-123 45 67' + * formatPhoneNumber('0303123456') // => '0303-12 34 56' + * ``` + */ +export function formatPhoneNumber(input: string): string { + const normalizedInput = cleanInput(input); + + if (MOBILE_PHONE_NUMBER_FORMAT.test(normalizedInput)) { + return normalizedInput.replace(MOBILE_PHONE_NUMBER_FORMAT, '$1-$2 $3 $4'); + } + + const areaCodeLength = TWO_DIGIT_AREA_CODE.test(normalizedInput) + ? 2 + : THREE_DIGIT_AREA_CODE.test(normalizedInput) + ? 3 + : 4; + + const areaCode = normalizedInput.substring(0, areaCodeLength); + const subscriberNumber = normalizedInput.substring(areaCodeLength); + + // if the subscriber number length is not in the formats, return the input as is + if (!(subscriberNumber.length in SUBSCRIBER_NUMBER_FORMATS)) { + return normalizedInput; + } + + const subscriberNumberFormat = + SUBSCRIBER_NUMBER_FORMATS[subscriberNumber.length]; + + const replacePattern = subscriberNumber.length === 5 ? '$1 $2' : '$1 $2 $3'; + + return `${areaCode}-${subscriberNumber.replace(subscriberNumberFormat, replacePattern)}`; +} const ORG_NUMBER_FORMAT = /^(\d{6})(\d{4})$/; diff --git a/packages/format/src/utils.ts b/packages/format/src/utils.ts index 80e96ee..58373b5 100644 --- a/packages/format/src/utils.ts +++ b/packages/format/src/utils.ts @@ -3,10 +3,14 @@ export function replaceIfMatch( regex: RegExp, replacerPattern: string, ): string { + const normalizedInput = cleanInput(input); + + return normalizedInput.replace(regex, replacerPattern); +} + +export function cleanInput(input: string): string { // We're extremely lenient when attemtping to format the input. // We remove everything that isn't a letter or a number, that way we can get rid of any // formatting that might already be present in the input, eg spaces, hyphens or dots - const cleaned = input.replace(/[^a-zA-Z0-9]/g, ''); - - return cleaned.replace(regex, replacerPattern); + return input.replace(/[^a-zA-Z0-9]/g, ''); } From 4ee69532aaae268768b3654f886af5e8f7075987 Mon Sep 17 00:00:00 2001 From: Alexander Bjerkan Date: Sun, 26 Jan 2025 22:02:44 +0100 Subject: [PATCH 3/3] ts --- packages/format/src/se.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/format/src/se.ts b/packages/format/src/se.ts index 295c07a..be33c8c 100644 --- a/packages/format/src/se.ts +++ b/packages/format/src/se.ts @@ -45,7 +45,10 @@ export function formatPhoneNumber(input: string): string { } const subscriberNumberFormat = - SUBSCRIBER_NUMBER_FORMATS[subscriberNumber.length]; + SUBSCRIBER_NUMBER_FORMATS[ + // the cast should be okay here, as we've checked the length above + subscriberNumber.length as keyof typeof SUBSCRIBER_NUMBER_FORMATS + ]; const replacePattern = subscriberNumber.length === 5 ? '$1 $2' : '$1 $2 $3';