Last active
April 4, 2025 01:07
-
-
Save Alex4386/e1f7d4391607860eb3e3ac3543cdbe4b to your computer and use it in GitHub Desktop.
한국 주민등록번호 파싱용 스크립트
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| const koNatlIDRegex = /^([0-9]{6})(-|)([0-9]{7})$/; | |
| function getBirthdayFromNatlID(idNo: string): { | |
| year: number; | |
| month: number; | |
| date: number; | |
| } | null { | |
| const formatValidityCheck = koNatlIDRegex.test(idNo); | |
| if (!formatValidityCheck) return null; | |
| const parsedResult = koNatlIDRegex.exec(idNo); | |
| const birthdayRegion = parsedResult[1]; | |
| const identifierRegion = parsedResult[3]; | |
| const birthdayParsed = /^([0-9]{2})([0-9]{2})([0-9]{2})$/.exec(birthdayRegion); | |
| if (!birthdayParsed) return null; | |
| const yearRegionIdentifier = identifierRegion.charAt(0); | |
| let yearRange = 0; | |
| switch (yearRegionIdentifier) { | |
| // backwards compatibility for 1800s | |
| case '9': | |
| case '0': | |
| yearRange = 1800; | |
| break; | |
| case '1': | |
| case '2': | |
| case '5': | |
| case '6': | |
| yearRange = 1900; | |
| break; | |
| case '3': | |
| case '4': | |
| case '7': | |
| case '8': | |
| yearRange = 2000; | |
| break; | |
| } | |
| if (yearRange === 0) return null; | |
| const year = parseInt(birthdayParsed[1], 10) + yearRange; | |
| const month = parseInt(birthdayParsed[2], 10); | |
| const date = parseInt(birthdayParsed[3], 10); | |
| return { | |
| year, | |
| month, | |
| date, | |
| }; | |
| } | |
| function getRegistrationDetailsFromNatlIdNo(idNo: string): null | { | |
| isForeigner: boolean; | |
| region: { | |
| raw: number; | |
| name: 'SEOUL' | 'BUSAN' | 'INCHEON' | 'GYEONGGI' | 'GANGWON' | 'CHUNGBUK' | 'DAEJEON' | 'CHUNGNAM' | 'SEJONG' | 'JEONBUK' | 'JEONNAM' | 'GWANGJU' | 'DAEGU' | 'GYEONGBUK' | 'GYEONGNAM' | 'ULSAN' | 'JEJU'; | |
| detail?: { | |
| isMetropolitan?: Boolean; | |
| }; | |
| }; | |
| office: { | |
| raw: number; | |
| }; | |
| registerOrder: number; | |
| } { | |
| const formatValidityCheck = koNatlIDRegex.test(idNo); | |
| if (!formatValidityCheck) return null; | |
| const parsedResult = koNatlIDRegex.exec(idNo); | |
| let isForeigner = false; | |
| const birthRangeId = parseInt(parsedResult[3].charAt(0)); | |
| switch (birthRangeId) { | |
| case 5: | |
| case 6: | |
| case 7: | |
| case 8: | |
| isForeigner = true; | |
| } | |
| let regionName: 'SEOUL' | 'BUSAN' | 'INCHEON' | 'GYEONGGI' | 'GANGWON' | 'CHUNGBUK' | 'DAEJEON' | 'CHUNGNAM' | 'SEJONG' | 'JEONBUK' | 'JEONNAM' | 'GWANGJU' | 'DAEGU' | 'GYEONGBUK' | 'GYEONGNAM' | 'ULSAN' | 'JEJU' | undefined = undefined; | |
| let isMetropolitan: boolean | undefined = undefined; | |
| const regionCode = parseInt(parsedResult[3].slice(1, 3)); | |
| if (0 <= regionCode && regionCode <= 8) { | |
| regionName = 'SEOUL'; | |
| } else if (9 <= regionCode && regionCode <= 12) { | |
| regionName = 'BUSAN'; | |
| } else if (13 <= regionCode && regionCode <= 15) { | |
| regionName = 'INCHEON'; | |
| } else if (16 <= regionCode && regionCode <= 18) { | |
| regionName = 'GYEONGGI'; | |
| isMetropolitan = true; | |
| } else if (19 <= regionCode && regionCode <= 25) { | |
| regionName = 'GYEONGGI'; | |
| isMetropolitan = false; | |
| } else if (26 <= regionCode && regionCode <= 34) { | |
| regionName = 'GANGWON'; | |
| } else if (35 <= regionCode && regionCode <= 39) { | |
| regionName = 'CHUNGBUK'; | |
| } else if (regionCode === 40) { | |
| regionName = 'DAEJEON'; | |
| } else if (regionCode === 44 || regionCode === 96) { | |
| regionName = 'SEJONG'; | |
| } else if (41 <= regionCode && regionCode <= 47) { | |
| regionName = 'CHUNGNAM'; | |
| } else if (48 <= regionCode && regionCode <= 54) { | |
| regionName = 'JEONBUK'; | |
| } else if (55 <= regionCode && regionCode <= 64) { | |
| regionName = 'JEONNAM'; | |
| } else if (65 <= regionCode && regionCode <= 66) { | |
| regionName = 'GWANGJU'; | |
| } else if (67 <= regionCode && regionCode <= 70) { | |
| regionName = 'DAEGU'; | |
| } else if (71 <= regionCode && regionCode <= 80) { | |
| regionName = 'GYEONGBUK'; | |
| } else if (regionCode === 85) { | |
| regionName = 'ULSAN'; | |
| } else if (81 <= regionCode && regionCode <= 90) { | |
| regionName = 'GYEONGNAM'; | |
| } else if (91 <= regionCode && regionCode <= 95) { | |
| regionName = 'JEJU'; | |
| } else { | |
| return null; | |
| } | |
| const officeCode = parseInt(parsedResult[3].slice(3, 5)); | |
| const registerOrder = parseInt(parsedResult[3].charAt(5)); | |
| return { | |
| isForeigner, | |
| region: { | |
| raw: regionCode, | |
| name: regionName, | |
| detail: isMetropolitan ? { | |
| isMetropolitan, | |
| } : undefined, | |
| }, | |
| office: { | |
| raw: officeCode, | |
| }, | |
| registerOrder, | |
| }; | |
| } | |
| function validateNatlIDNo(idNo: string): boolean { | |
| const formatValidityCheck = koNatlIDRegex.test(idNo); | |
| if (!formatValidityCheck) return false; | |
| const parsedResult = koNatlIDRegex.exec(idNo); | |
| if (parsedResult === null) return false; | |
| const data = parsedResult[1] + parsedResult[3]; | |
| const checkSum = parseInt(parsedResult[3].charAt(6)); | |
| let checkSumCalcTempSum = 0; | |
| for (let i = 0; i < 8; i++) { | |
| const thisCharacter = data.charAt(i); | |
| const multiplyTarget = (i + 2); | |
| checkSumCalcTempSum += (parseInt(thisCharacter, 10) * multiplyTarget); | |
| } | |
| for (let offset = 0; offset < 4; offset++) { | |
| const thisCharacter = data.charAt(offset + 8); | |
| const multiplyTarget = (offset + 2); | |
| checkSumCalcTempSum += (parseInt(thisCharacter, 10) * multiplyTarget); | |
| } | |
| const checkSumMod = checkSumCalcTempSum % 11; | |
| const calculatedCheckSum = (11 - checkSumMod) % 10; | |
| return calculatedCheckSum === checkSum; | |
| } | |
| function parseNatlId(idNo: string) { | |
| const formatValidityCheck = koNatlIDRegex.test(idNo); | |
| if (!formatValidityCheck) return null; | |
| return { | |
| isValid: validateNatlIDNo(idNo), | |
| birthday: getBirthdayFromNatlID(idNo), | |
| details: getRegistrationDetailsFromNatlIdNo(idNo), | |
| }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment