Available as NuGet package: https://www.nuget.org/packages/Tedd.Fodselsnummer
Code to verify Norwegian national id number (fødselsnummer / personnummer) written in C#.
Obeys 2013 standards as described in this Wikipedia article. Please let me know how if you find bugs / improvements. 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
public static class NorwegianPersonalNumberValidator { // Implementation of Norwegian personal number verification based on specifications outlined on https://no.wikipedia.org/wiki/F%C3%B8dselsnummer // Version 1: 2016-12-07 Tedd Hansen, [email protected] public class Result { public enum GenderType { Male, Female } [Flags] public enum NumberType { Normal = 0, D = 1, H = 2, DAndF = 3, // Not valid FH = 4, DAndFG = 5, // Not valid HAndFH = 6, // Not valid DAndHAndFH = 7 // Not valid } public bool Success { get; set; } public string ErrorMessage { get; set; } public GenderType Gender { get; set; } public NumberType Type { get; set; } public DateTime Birthday { get; set; } public override string ToString() { if (Success) return $"Success: {Success}, Birthday: {Birthday.ToString("yyyy'-'MM'-'dd")}, Gender: {Gender}, Type: {Type}"; else return $"Success: {Success}, ErrorMessage: {ErrorMessage ?? "N/A"}"; } } private struct IndividualNumberControlRange { public int From; public int To; public int FromYear; public int ToYear; } private static readonly IndividualNumberControlRange[] IndividualControlRange = new[] { new IndividualNumberControlRange() { From = 0, To = 499, FromYear = 1900, ToYear = 1999 }, new IndividualNumberControlRange() { From = 500, To = 749, FromYear = 1854, ToYear = 1899 }, new IndividualNumberControlRange() { From = 500, To = 999, FromYear = 2000, ToYear = 2039 }, new IndividualNumberControlRange() { From = 900, To = 999, FromYear = 1940, ToYear = 1999 }, }; private static Regex PersonalNumberRegex = new Regex(@"^(?<birthdate>(?<day>\d\d)(?<month>\d\d)(?<year>\d\d))(?<individual>\d\d(?<gender>\d))(?<checksum>\d\d)$"); public static Result Validate(string number) { var result = new Result(); // Is it the correct length? if (number == null || number.Length != 11) return new Result() { Success = false, ErrorMessage = "Feil lengde. Personnummer er 11 tall." }; // Extract what we need var match = PersonalNumberRegex.Match(number); // Regex is only looking for 11 numbers, so we can assume any error is because there are non-numbers if (!match.Success) return new Result() { Success = false, ErrorMessage = "Personummer skal kun inneholde tall." }; // Parse the numbers var day = int.Parse(match.Groups["day"].Value); var month = int.Parse(match.Groups["month"].Value); var year = int.Parse(match.Groups["year"].Value); var individual = int.Parse(match.Groups["individual"].Value); var gender = int.Parse(match.Groups["gender"].Value); var checksum = int.Parse(match.Groups["checksum"].Value); // Compensate for D-numbers if (day > 31) { result.Type |= Result.NumberType.D; day -= 40; } // Compensate for H-numbers if (month > 30) { result.Type |= Result.NumberType.H; month -= 40; } // Compensate for FH-numbers if (day >= 80) { result.Type |= Result.NumberType.FH; day = 1; } // Determine gender (not that we need it) result.Gender = gender % 2 != 0 ? Result.GenderType.Male : Result.GenderType.Female; int fullYear = 0; // Get the range based on the individual range var ranges = IndividualControlRange.Where(r => individual >= r.From && individual <= r.To).ToList(); if (ranges == null || ranges.Count == 0) return new Result() { Success = false, ErrorMessage = "Feil i personnummer. (#3)" }; foreach (var range in ranges) { // Get full year based on the range // Note that if there is a change to range so it crosses centuries then we need more checking here - or else the next check will fail var fYear = int.Parse(range.FromYear.ToString().Substring(0, 2) + year.ToString("D2")); // Check that we are within allowed range if (fYear >= range.FromYear && fYear <= range.ToYear) { fullYear = fYear; break; } } if (fullYear == 0) return new Result() { Success = false, ErrorMessage = "Feil i personnummer. (#4)" }; // Have .Net verify the date, using ISO 8601 should be universally safe var iso8601Birthday = fullYear + "-" + month.ToString("D2") + "-" + day.ToString("D2"); DateTime dtBirthday; if (!DateTime.TryParse(iso8601Birthday, out dtBirthday)) return new Result() { Success = false, ErrorMessage = "Feil i personnummer. (#5)" }; result.Birthday = dtBirthday; // And finally calculate and verify the two checksum digits at the end of the number // Convert personal number string to int array var n = number.Select(c => int.Parse(c.ToString())).ToArray(); // Calculate checksum number 1 int k1 = 11 - (3 * n[0] + 7 * n[1] + 6 * n[2] + 1 * n[3] + 8 * n[4] + 9 * n[5] + 4 * n[6] + 5 * n[7] + 2 * n[8]) % 11; if (k1 == 11) k1 = 0; if (k1 == 10 || k1 != n[9]) return new Result() { Success = false, ErrorMessage = "Feil i personnummer. (#6)" }; // Calculate checksum number 2 int k2 = 11 - (5 * n[0] + 4 * n[1] + 3 * n[2] + 2 * n[3] + 7 * n[4] + 6 * n[5] + 5 * n[6] + 4 * n[7] + 3 * n[8] + 2 * k1) % 11; if (k2 == 11) k2 = 0; if (k2 == 10 || k2 != n[10]) return new Result() { Success = false, ErrorMessage = "Feil i personnummer. (#7)" }; result.Success = true; return result; } } void Main() { // Test a whole lot of numbers... foreach (var pn in new[] { "11056027043", "01073703642", "11123434945", "19073120317", "02012526582", "01052304454", "28023727473", "07052739084", "03126320255", "13106215582", "07032425814", "26082416446", "15060238585", "30121408178", "27096212181", "23021347574", "20064234219", "15081024143", "26045230189", "10082000734", "02012048281", "20046647611", "29096114654", "10026402781", "06122615721", "17093725573", "04121422528", "04024403756", "20101229085", "19092040383", "23053739865", "24101534665", "12021509637", "29011203424", "18100810960", "22040112832", "18101938626", "16021315420", "18060835965", "26050311189", "27094110284", "21112531448", "28015703069", "21122141931", "30062121293", "10095324709", "03129788495", "19123924293", "24020317082", "26085821228", "26093737317", "29123535985", "29034704358", "05079764185", "11022315392", "01070615963", "19123122669", "21072401511", "22054406766", "09104338078", "18062248188", "30030827720", "30112309574", "13052429840", "18071235706", "21031838069", "27124620754", "06024248559", "03033029178", "23123301676", "02034006841", "21072007651", "02125218752", "09036030736", "17021721999", "08023716837", "30031710606", "08116344379", "27116322930", "15071437741", "17093343433", "13030820546", "13044940323", "07052826904", "04072204420", "19116213748", "23109866350", "05012118130", "21094016352", "25095110668", "11123630884", "01114934301", "10022530181", "19056102216", "06025612378", "24074641810", "01040326363", "03034908183", "13032149086", "02114412077", "18080406424", "17072630378", "04092334681", "29080604899", "25123841185", "10113841724", "12065136587", "06030310878", "22020526134", "13102022887", "11115532164", "02072342685", "28114002692", "29104548448", "28052326658", "18116435868", "27044618252", "22024417623", "23022628828", "26034940583", "06051220313", "11042224601", "07083106608", "09039858808", "20022534093", "13035932571", "07081439073", "08053604146", "07073128735", "26096220609", "31075807301", "03022413723", "07063723511", "26066222992", "06084708613", "23013117187", "29073019808", "24062118284", "16032844042", "15123629776", "14123902867", "16035907215", "07095638052", "11034806901", "22126334676", "05031049115", "30092903458", "20034734152", "11063819206", "25055243754", "27035620058", "21051146074", "20094511330", "05059959494", "16053718144", "04124348973", "19112526393", "15065744985", "04106439600", "10121512268", "11020023335", "08081519855", "05075030136", "07056207850", "11115902573", "02042847839", "16100324044", "06075042491", "21079891638", "18070849859", "01015033106", "19072248530", "12023702809", "27080408124", "18044930346", "22100127346", "16119888232", "27033837831", "04042622287", "14034120723", "15014131539", "20035421794", "18115548537", "23064702998", "02024939521", "24030549194", "28094723177", "12030246126", "28063144225", "08052621187", "01110836399", "19011233360", "20101138829", "11053336886", "12114423309", "10070343334", "14093948247", "07043538963", "28014503937", "19070428914", "01110540457", "27061246180", "27056341256", "13105013773", "27036345107", "31082817118", "06115110088", "15105730222", "20052542872", "16096010048", "25122123025", "10094641611", "11034548759", "16103839568", "17100930382", "11094129240", "02022029110", "17106242046", "12106207591", "23074421733", "06123642862", "23126244819", "13124902694", "08081101885", "22015211354", "21013130903", "02122305093", "23010019192", "29044201982", "24100324632", "24013846509", "21024927722", "25106340725", "16083327239", "31034007376", "15121411005", "20015216191", "25113637032", "26075821742", "18060828616", "29051214674", "19126131146", "26043134476", "20124131643", "29075545310", "29094040734", "01055524900", "29076437376", "27040714753", "10096633292", "04099754678", "06081102277", "08069899936", "08021834301", "18059884715", "18045613433", "25096416248", "25120211954", "26040910149", "05086131120", "22033447179", "12045314845", "02122312332", "25096647819", "21030620507", "26101618802", "14073911886", "04115003416", "25111041191", "11069684981", "01033241638", "07114207610", "14090225597", "10011130785", "21084015145", "18065138693", "21070305647", "25100337862", "30060807694", "08091021456", "19052803538", "16126241708", "19029750915", "19052047725", "15065819209", "02013245013", "28082011120", "27086345590", "12080322230", "29115414354", "30091342681", "23104817351", "18050542919", "22063729772", "10075347206", "16084800833", "09024430097", "01024032112", "29041435516", "08115302893", "05074909924", "19099758808", "03062045476", "10094712861", "11053907778", "20062606097", "19102611627", "27106512758", "22059952793", "19060604080", "11111513424", "24030202607", "14072424973", "11110422220", "12010841346", "13126129011", "10123039667", "31053648854", "29101736693", "30093149412", "09060608617", "07111913875", "18030536645", "19123804832", "15022305376", "13031915419", "15106031603", "15012828958", "26040746632", "31125107454", "28021640524", "31034442404", "09101119162", "08072518595", "15122923778", "02052538788", "21104313105", "21095508070", "02114908889", "16102948053", "02013914853", "13041911578", "23101716481", "09076347295", "03084733708", "14029668915", "17014122842", "08121512794", "22010539092", "11013406253", "09115212289", "04059650756", "24094335554", "17084615870", "25055616100", "11023346283", "14036438143", "24096131476", "22033837937", "23124303443", "22094532470", "23075543980", "23030123395", "18109671580", "10081235622", "20012049200", "30011228476", "30093913872", "29091211910", "01061730466", "12095347930", "10010933094", "22091122883", "03056626505", "18072343111", "14084414019", "17062318252", "21042831856", "03062007698", "16021847171", "25031247198", "03055615054", "16061512628", "31059997591", "10090645116", "15075346317", "29115839673", "10031546643", "21053445451", "05125424823", "23044029804", "10066417358", "26041503873", "30015003363", "15090710157", "12011248851", "14041024063", "04081813839", "31036116946", "07114921801", "05074332090", "02073642070", "02123322404", "19102233509", "10039651837", "24122702606", "31082308469", "11113122333", "04109679920", "22020342378", "20114343235", "19083010404", "09020424386", "20084610225", "08014748289", "14125332715", "10066343737", "05026324388", "28079678793", "11086630941", "01121319434", "17092608131", "14112442643", "23112748318", "15064031225", "04045736933", "05031643602", "30084722614", "11012503603", "19124843947", "20070510635", "08024502334", "29112218511", "01080036686", "08066217095", "03103928582", "14054946709", "01040135258", "19080024161", "14034440035", "01056204950", "19021710949", "01014138419", "01085116874", "18034724292", "04079883218", "16084343351", "06112416593", "13102701318", "04100422277", "21075330003", "08026249539", "01060044496", "11103922633", "31030741075", "05062220564", "16085700174", "13044233996", "03064148671", "04104513102", "07055033352", "17112946667", "20113429144", "09106040696", "09113322559", "07079879538", "01064723444", "13044342086", "15101644283", "29035205509", "29083306236", "06105524984", "27080734072", "27011202581", "05119689084", "25055430252", "11113026334", "24043924482", "14020945185", "15122248159", "10065644555", "06109770173", "26031828677", "28113924669", "28021433677", "28112044436", "14064322827", "28090226136", "24123309487", "10122611766", "07036327736", "13033525707", "24114832339", "21074331898", "13120716705", "27112512654", "23083844418", "21094311257", "10036115233", "04129951651" }) Console.WriteLine(pn + " " + NorwegianPersonalNumberValidator.Validate(pn)); // Some of these fail because individual-number falls within range specifications modified since 2013 foreach (var pn in new[] { "10083707696", "13093347166", "25082347197", "26109815046", "22022912694", "18031445155", "16030389615", "07092846657", "31059624876", "04077129877", "26112803822", "08121510198", "23042348880", "27063413890", "09030766986", "06108721853", "14127632058", "12086022409", "28084133568", "24049651844", "28065002622", "19024308416", "07025722390", "29020842316", "06087743258", "20100924359", "29119751495", "25059992409", "07056334208", "09128248274", "01109432780", "02121093742", "05115905424", "28120468229", "29051098151", "10072619469", "29044808119", "07023610501", "11034542645", "05055048314", "02107131240", "20050086985", "16053043643", "03121733217", "12018422660", "17064503634", "27090297444", "03048643964", "05041210674", "05097902315", "07114118537", "17030967038", "22077028521", "08128646702", "17076614105", "06120435254", "19110324122", "17027840000", "10010954903", "04013210964", "05097238490", "16099016802", "06048324272", "05040033272", "24051623408", "23027743323", "25024516515", "25025001357", "30111022298", "25064808071", "26019430072", "24080899544", "07070344453", "13111347470", "18077042063", "05087022635", "24021541008", "08014120557", "21050083443", "03010150123", "12117433941", "31075014360", "18026418417", "14129766973", "24057348689", "09088833322", "22095346603", "04046229113", "06097808951", "17050690660", "03076022568", "21127402415", "12088323473", "25077642032", "02063144269", "02119105563", "02036006202", "01119724933", "06111534096", "18052302559", "13028929743", "05079530214", "20053746420", "26055526353", "09051845177", "04124148184", "28129844541", "30129870172", "15113328697", "25107024606", "09122633672", "05095300180", "08117622828", "23060726175", "17096009437", "19094811778", "07125437154", "23061077731", "14106225746", "20028632305", "10017731896", "08051616759", "30078506187", "15124612672", "30086844466", "01020765271", "06090201628", "30060409702", "15029030755", "19109331425", "09115512231", "07099808351", "11011458817", "26073736543", "28086016372", "14082849697", "18056009597", "28092209042", "31019831259", "01078941584", "26045905194", "01037408367", "22103232094", "15010940661", "25068944891", "21061606855", "30048711215", "10020879985", "28065614792", "18074300746", "18126104185", "27085129002", "15098749907", "19097622577", "30100422081", "02032406192", "16030761387", "10121061746", "26046935372", "06044544812", "07052131469", "19092748777", "30058415517", "22124447589", "03080235630", "18017605891", "18065504304", "24108217045", "06100542887", "10049103900", "20057238306", "20090032314", "22055913432", "16038836483", "20092608078", "14048424105", "12107944298", "04113432638", "09011416605", "29071641145", "06125519383", "02033905079", "13028938599", "02101285076", "05116220212", "08122038867", "25034718558", "05020678226", "13014030241", "17095234623", "20010965579", "19093536447", "27010983466", "19110648394", "11123927267", "22024324951", "03067614983", "21046341837", "04117204702", "01093732827", "28041172276", "25032128125", "24095542197", "13025443424", "28017207638", "18080161642", "23030671635", "08047331561", "01111428634", "30109998849", "01084922087", "26034431158", "11098942590", "17031822874", "07082123649", "16090761750", "08051363400", "20117126642", "17021703117", "11063234178", "09098723339", "15022341739", "06120822541", "24112342946", "27129520855", "19117808524", "06082525856", "11111346013", "08042703002", "04027611996", "17121254816", "27112533988", "16120606577", "17070633388", "05124029475", "23120874934", "08072129297", "07110840777", "08053328998", "16080955913", "28044226865", "29038936725", "19113649243", "06081838729", "22035212713", "11058304818", "07023640095", "08090987667", "20110123184", "18079407514", "16081931228", "15036018264", "28127416615", "22112437731", "20064120132", "28030568100", "03129134282", "31076710131", "08124247005", "10105937852", "25120006331", "23085711822", "14019638856", "25080605040", "21045441234", "25030831694", "22102645220", "21034242078", "28088406925", "03062302916", "05121706425", "14120267133", "26123546724", "06113319486", "07057332578", "22121348458", "21063847345", "30092814455", "31080685591", "22089511511", "22082246456", "23108110864", "24110674194", "23111463719", "31084643028", "07095645997", "13022543460", "29121626428", "01056941033", "07063711467", "20010016895", "25113640955", "11083912148", "07066618832", "06082743640", "14069776281", "09120022854", "31032647467", "04031837907", "24115907529", "02067448599", "23053924190", "19010792439", "09071169925", "26019205796", "07024548702", "15031053983", "13126733814", "24012027482", "01090168424", "01066847973", "05031938165", "02028403286", "15126503943", "15010472919", "23072128702", "28040770676", "21024930545", "13039639771", "23013331464", "19010360970", "07100739873", "02016040846", "04107244854", "30010112199", "12073523212", "27031397443", "29092725837", "12043915779", "17051630602", "17036036923", "20017312228", "10018507302", "01011149614", "03116638648", "30011588596", "05036133696", "20043143469", "16103517579", "22078812501", "30090943290", "16076024557", "13032314214", "01127722451", "13067742286", "13103543295", "09013932902", "25128524013", "06060193299", "24011289807", "20067305524", "15122835054", "24029669356", "23084822086", "14078315823", "06112612422", "09041080970", "29119612398", "14025200404", "10072522856", "08089116164", "25021053611", "08100996279", "01021566809", "30063706783", "10010720023", "29109604184", "03080912322", "30095226589", "16129639737", "18100691481", "14023908175", "17029866368", "03050795127", "19021230394", "10030642371", "18079435844", "25043631112", "06051498257", "20010811784", "12055743760", "01066249707", "06058943590", "01046330877", "21100345363", "20070807875", "04091134026", "17090665367", "24115231568", "14103114560", "08118020597", "28077431847", "21063719770", "14016015793", "23022546740", "30034806329", "14098543903", "17060131201", "20018235812", "08041304813", "17070290324", "17069049745", "01067816605", "06035435660", "17054933878", "22015237574", "26026620642", "22124143857", "13039703429", "22068723942", "02109438661", "20103007144", "07087331320", "17082321217", "02067144352", "29106604442", "26069772985", "05032814044", "30099030913", "22090036061", "15022327140", "23083929723", "05033424223", "14040991329", "13112921103", "06107343030", "25020297789", "16125021208", "15126600140", "03034247667", "17110482302", "24082812268", "16124516874", "13036349292", "04049980979", "23115107623", "17107147616", "06049629069", "25107322577", "21064541983", "07108343340", "07080668958", "29071543992", "18110254675", "17105516770", "25050881507", "13107600978", "06112223981", "25030192780", "21071644542", "10066326344", "10108731791", "21065328922", "05086413088", "20128028227", "11085021658", "24054403859", "15017008169", "25020744874", "19020233403", "11090881630", "25037546316", "07079135377", "22021218662", "22010683347", "25086327093", "27099647286", "25010443876", "28040663306", "13012532533", "17097020663", "27117522143", "08103642265", "24056001588", "03068818612", "12066541817", "06049824457", "12021915813", "24030895300", "29108145682", "07016737369", "30086437577", "29017338127", "03011362329", "27100131795", "20111010758", "08079335771", "11076010082", "18028944272", "12017224601", "03094619784", "17086849011", "05088745258", "19022403403", "29039536941", "11024645470", "27061366984", "14064536177" }) Console.WriteLine(pn + " " + NorwegianPersonalNumberValidator.Validate(pn)); } |
(PS: This code is easy to test in LINQPad. Select C# Program, paste it in and hit run.)
Hi Tedd,
I am trying to apply similar logic to a Teradata SQL query. What I am trying to find out is how the 2nd checksum is calculated if the 1st produces an “X” i.e. the first checksum was 10 which is replaced with an “X” (according to several sources in Wiki, Google etc).
e.g.
181251490 becomes 122 when multiplying each digit by the weight and adding the result.
122/11 = 11 remainder 1
11-1 = 10
Check digit is therefore X
181251490 becomes 181251490X
So I need to understand how to use mod11 on 181251490X
(one source: <a href="http://www.pgrocer.net/Cis51/mod11.html" rel="nofollow ugc">http://www.pgrocer.net/Cis51/mod11.html</a>)
Thanks,
Jonny
Hi
For Norwegian birth numbers there is no X, only digits. If the mod11 result is 11 its changed to 0, if result is 10 then number is invalid. Second control digit follows the same rule with multipliers vs indexes changed.
Br,
Tedd
Thank you, I was looking for test data, and oddly enough could not find it anywhere else.
Long shot: Do you know where to find company numbers?
/Terje
You can download Brønnøysundregisteret. <a href="https://www.brreg.no/produkter-og-tjenester/apne-data/" rel="nofollow ugc">https://www.brreg.no/produkter-og-tjenester/apne-data/</a> see “Last ned” links at <a href="https://data.brreg.no/enhetsregisteret/oppslag/enheter" rel="nofollow ugc">https://data.brreg.no/enhetsregisteret/oppslag/enheter</a>