public static class NorwegianPersonalNumberValidator
{
// Implementation of Norwegian personal number verification based on specifications outlined on https://no.wikipedia.org/wiki/F%C3%B8dselsnummer
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));
}