Fødselsdato fra fødselsnummer

Til min store frustrasjon, fant jeg ikke noen kode på nettet som dro ut fødselsdatoen fra et fødselsnummer. Det er verre enn det høres ut, med 18., 19. og 20. århundre, D-numre og H-numre. Men jeg fant denne siden http://no.wikipedia.org/wiki/F%C3%B8dselsnummer (http://no.wikipedia.org/wiki/Personnummer).

Dette er offentlig informasjon og må da være brukt av mange, tenker jeg, men den gang ei. Nå, da jeg har implementert det, skjønner jeg forsåvidt at det ikke er publisert, det er jo bare noen få if-setninger! Men med min frustrasjon friskt i minne, publiserer jeg, sammen med en rekke enhetstester for corner-caser. Om jeg har gjort noe feil, vil jeg gjerne vite det!

OBS: Jeg har ignorert sjekksummen, da jeg ikke hadde behovet for å validere selve fødselsnummeret. Jeg er kun interessert i fødselsdatoen. Sjekksumformelen finner du i wikisiden om du ønsker å implementere dette. Send meg gjerne klassen med tester om du implementerer det.

Implementasjonen er i engelsk, da det er slik jeg koder til vanlig. Kunne med fordel brukt norsk her.

package utilities;

import org.joda.time.DateTime;

/**
 * Fødselsnummer is an eleven digit registration number assigned by
 * the norwegian government to all it's citizens. Other countries have
 * similar numbers. Sweden has Personnummer, Denmark has CPR-nummer,
 * Finland has Personbeteckning, GB has National Insurance number and
 * USA has Social Security number.
 *
 * (http://no.wikipedia.org/wiki/F%C3%B8dselsnummer)
 * (http://no.wikipedia.org/wiki/Personnummer)
 *
 *
 * The six first digits normally represents the birth date: day (dd),
 * month (mm) and year (yy). However, the two digit representation of
 * the year has caused many problem (read the Y2k-problem) and isn't
 * sufficent to decide the century. Therefore, the three next digits
 * that are the individsifre, are categorized to represent time
 * periods. * Fødselsnummer: ddmmyyiiicc (d day, m month, y year, i
 * individsiffer, c checksum)
 *
 */
public final class FodselsnummerUtility {

  private FodselsnummerUtility() {
  }

  public static DateTime nationalIdentificationNumberToDate(String pnr) {
    int individsifre = Integer.parseInt(pnr.substring(6, 9));
    int day = Integer.parseInt(pnr.substring(0, 2));
    int birthday;
    if (day > 40) {
      // D-nummer
      birthday = day - 40;
    } else {
      birthday = day;
    }
    int month = Integer.parseInt(pnr.substring(2, 4));
    int birthmonth;
    if (month > 40) {
      // H-nummer
      birthmonth = month - 40;
    } else {
      birthmonth = month;
    }
    int year = Integer.parseInt(pnr.substring(4, 6));
    int century;
    if (individsifre >= 0 && individsifre <= 499) {
      // 000-499 is 1900-1999
      century = 19;
    } else if (individsifre >= 500 && individsifre <= 749
        && year >= 55) {
      // 500-749 is 1855-1899
      century = 18;
    } else if (individsifre >= 500 && individsifre <= 999
        && year <= 39) {
      // 500-999 is 2000-2039
      century = 20;
    } else if (individsifre >= 900 && individsifre <= 999
        && year >= 40) {
      // 900-999 is 1940–1999
      century = 19;
    } else {
      throw new IllegalArgumentException(
          "cannot convert the national identification number (" + pnr
              + ") to a birth date");
    }
    year = century * 100 + year;
    return new DateTime(year, birthmonth, birthday, 0, 0, 0, 0);
  }

  public static String convertToBirthDateString(String fnr,
      String individsifre, String checksum) {
    String pnr = fnr + individsifre + checksum;
    DateTime dateTime = nationalIdentificationNumberToDate(pnr);
    return DateConverterUtility.dateToDdmmyyyy(dateTime);
  }
}

En annen utility klassen som inneholder mye datomanipulasjon(jeg er dritt lei dato- og tegnsettproblematikk om du lurte…). Kun tatt med metoden benyttet over.

package utilities;
import org.joda.time.DateTime;

public final class DateConverterUtility {

    public static String dateToDdmmyyyy(DateTime dateTime) {
        String day;
        String month;
        if (dateTime.getDayOfMonth() < 10) {
            day = "0" + dateTime.getDayOfMonth();
        } else {
            day = "" + dateTime.getDayOfMonth();
        }

        if (dateTime.getMonthOfYear() < 10) {
            month = "0" + dateTime.getMonthOfYear();
        } else {
            month = "" + dateTime.getMonthOfYear();
        }
        return day + "." + month + "." + dateTime.getYear();
    }
}

Tester

package utilities;

import static org.junit.Assert.*;

import org.junit.Before;
import org.junit.Test;

public class FodselsnummerUtilityTest {
  private String checksum;

  @Before
  public void setup() {
    // Basic: no checksum. Tests ignore it
    checksum = "00";
  }

  @Test
  public void fnrForPersonsBorn1900To1999() {

    String fnr;
    String individsifre;
    String birthdate;
    // --------
    // 1900-1999 has individsifre 000-499
    fnr = "170780";
    individsifre = "000";
    birthdate = "17.07.1980";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

    individsifre = "499";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

    fnr = "011280";
    individsifre = "000";
    birthdate = "01.12.1980";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

  }

  @Test
  public void fnrForPersonsBorn1855To18999() {
    // -------------
    // 1855-1899 has individsifre 500-749
    String fnr = "170780";
    String individsifre = "500";
    String birthdate = "17.07.1880";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

    individsifre = "749";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

  }

  @Test
  public void fnrForPersonsBorn2000To2039() {
    // -------------
    // 2000-2039 has individsifre 500-999
    String fnr = "170700";
    String birthdate = "17.07.2000";
    String individsifre = "500";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

    individsifre = "749";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

    individsifre = "999";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

    fnr = "170739";
    birthdate = "17.07.2039";
    individsifre = "999";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

  }

  @Test
  public void fnrForPersonsBorn1940To1999() {
    // -------------
    // 1940-1999 has individsifre 900-999
    String fnr = "170740";
    String birthdate = "17.07.1940";

    String individsifre = "900";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));

    individsifre = "999";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));
  }

  @Test
  public void fnrUnkownIndividSifreButNormalDate() {
    String fnr = "170780";
    // Seems reasonable, but falls outside of the rules.
    String individsifre = "899";
    try {
      FodselsnummerUtility.convertToBirthDateString(fnr,
          individsifre, checksum);
      fail();
    } catch (IllegalArgumentException iae) {
      assertNotNull(iae);
    }
  }

  @Test
  public void fnrIllegalDay() {
    String fnr = "320780";
    // Seems reasonable, but falls outside of the rules.
    String individsifre = "123";
    try {
      FodselsnummerUtility.convertToBirthDateString(fnr,
          individsifre, checksum);
      fail();
    } catch (IllegalArgumentException iae) {
      assertNotNull(iae);
    }
  }

  @Test
  public void fnrIllegalMonth() {
    String fnr = "171380";
    // Seems reasonable, but falls outside of the rules.
    String individsifre = "123";
    try {
      FodselsnummerUtility.convertToBirthDateString(fnr,
          individsifre, checksum);
      fail();
    } catch (IllegalArgumentException iae) {
      assertNotNull(iae);
    }
  }

  @Test
  public void fnrBirthDateFromDNummer() {
    // D-nummer is + 4 to the first digit
    String fnr = "570780";

    String birthdate = "17.07.1980";
    String individsifre = "100";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));
  }

  @Test
  public void fnrIllegalDNummer() {
    String fnr = "730780";
    String individsifre = "123";
    try {
      FodselsnummerUtility.convertToBirthDateString(fnr,
          individsifre, checksum);
      fail();
    } catch (IllegalArgumentException iae) {
      assertNotNull(iae);
    }
  }

  @Test
  public void fnrBirthDateFromHNummer() {
    // H-nummer is +4 to third digit
    String fnr = "174780";
    String individsifre = "100";
    String birthdate = "17.07.1980";
    assertEquals(birthdate, FodselsnummerUtility
        .convertToBirthDateString(fnr, individsifre, checksum));
  }

  @Test
  public void fnrIllegalHNummer() {
    // H-nummer is +4 to third digit
    String fnr = "175380";
    String individsifre = "100";
    try {
      FodselsnummerUtility.convertToBirthDateString(fnr,
          individsifre, checksum);
      fail();
    } catch (IllegalArgumentException iae) {
      assertNotNull(iae);
    }
  }
}
About these ads

About Ole Morten Amundsen

Developer, programmer, entrepreneur. Java, .Net, ruby, rails, agile, lean. Opinionated enthusiast!
This entry was posted in Java and tagged , , , , , . Bookmark the permalink.

2 Responses to Fødselsdato fra fødselsnummer

  1. Terje Mathisen says:

    Jeg tror det er noe feil her:

    } else if (individsifre >= 900 && individsifre = 40) {
    // 900-999 is 2000-2039
    century = 19;

    Enten koden eller kommentaren er vel gal?

    Terje

  2. Ole Morten says:

    Takk. kommentar endret 1940–1999

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s