diff --git a/LICENSE b/LICENSE
index 2e08098..46a61ed 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017-2019 - Personnummer and Contributors
+Copyright (c) 2017-2020 - Personnummer and Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index c3f39bb..ea496da 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,91 @@
# Personnummer
-[![Build Status](https://travis-ci.org/personnummer/java.svg?branch=master)](https://travis-ci.org/personnummer/java)
+[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/personnummer/java/Test)](https://github.com/personnummer/java/actions)
-Validate Swedish personal identity numbers
+Validate Swedish personal identity numbers.
-## Example
+## Installation
-```java
-class Test {
- public void main(String[] args){
- Personnummer.valid(6403273813L); // => True
- Personnummer.valid("19130401+2931"); // => True
+Add the github repository as a Maven or Gradle repository:
+
+```xml
+
+ dev.personnummer
+ personnummer
+ 3.*.*
+
+```
+
+```groovy
+plugins {
+ id 'maven'
+}
+
+repositories {
+ maven {
+ url "https://github.com/personnummer/java:personnummer"
}
}
+
+dependencies {
+ configuration("dev.personnummer:personnummer")
+}
+```
+
+For more information on how to install and authenticate with github packages, check [this link](https://help.github.com/en/packages/using-github-packages-with-your-projects-ecosystem/configuring-apache-maven-for-use-with-github-packages).
+
+## Examples
+
+### Validation
+
+```java
+import dev.personnummer.*;
+
+class Test
+{
+ public void TestValidation()
+ {
+ Personnummer.valid("191212121212"); // => True
+ Personnummer.valid("12121+21212"); // => True
+ Personnummer.valid("2012121-21212"); // => True
+ }
+}
+```
+
+### Format
+
+```java
+// Short format (YYMMDD-XXXX)
+(new Personnummer("1212121212")).format();
+// => 121212-1212
+
+// Short format for 100+ years old
+(new Personnummer("191212121212")).format();
+//=> 121212+1212
+
+// Long format (YYYYMMDDXXXX)
+Personnummer.parse("1212121212").format(true);
+//=> 201212121212
+```
+
+### Age
+
+```java
+(new Personnummer("1212121212")).getAge();
+//=> 7
+```
+
+### Get sex
+
+```java
+(new Personnummer("1212121212")).isMale();
+//=> true
+Personnummer.parse("1212121212").isFemale();
+//=> false
```
-See [`src/test/java/PersonnummerTest.java`](src/test/java/PersonnummerTest.java) for more examples.
+See `src/test//PersonnummerTest.java` for more examples.
## License
-[MIT](LICENSE)
+[MIT](https://github.com/personnummer/java/blob/master/LICENSE)
diff --git a/build.gradle b/build.gradle
index 26cf9f9..f5b7c23 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,20 +1,19 @@
plugins {
- id("maven-publish")
+ id 'maven-publish'
+ id 'java-library'
+ id 'java'
}
-apply plugin: 'java-library'
-apply plugin: 'java'
-
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
- jcenter()
+ mavenCentral()
}
dependencies {
- testImplementation 'junit:junit:4.12'
- testCompile ("junit:junit:4.12", "org.json:json:20200518")
+ testImplementation('org.junit.jupiter:junit-jupiter:5.6.2')
+ testCompile('org.junit.jupiter:junit-jupiter:5.6.2', "org.json:json:20200518")
}
@@ -27,6 +26,7 @@ jar {
}
test {
+ useJUnitPlatform()
testLogging {
events "passed", "skipped", "failed"
exceptionFormat "full"
diff --git a/src/main/java/Personnummer.java b/src/main/java/Personnummer.java
deleted file mode 100644
index ac717bb..0000000
--- a/src/main/java/Personnummer.java
+++ /dev/null
@@ -1,97 +0,0 @@
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Class used to validate Swedish personal identity numbers.
- *
- * @author Johannes Tegnér
- */
-public final class Personnummer {
- private static final Pattern regexPattern;
-
- static {
- regexPattern = Pattern.compile("^(\\d{2})?(\\d{2})(\\d{2})(\\d{2})([-|+]?)?((?!000)\\d{3})(\\d?)$");
- }
-
- private Personnummer() {
- throw new AssertionError("Class cannot be instantiated");
- }
-
- /**
- * Validate a Swedish personal identity number.
- *
- * @param value personal identity number to validate, as string.
- * @return True if valid.
- */
- public static boolean valid(String value) {
- if (value == null) {
- return false;
- }
-
- Matcher matches = regexPattern.matcher(value);
- if (!matches.find()) {
- return false;
- }
-
- int year, month, day, control, number;
- try {
- String y = matches.group(2);
- year = Integer.parseInt((y.length() == 4 ? y.substring(2) : y));
- month = Integer.parseInt(matches.group(3));
- day = Integer.parseInt(matches.group(4));
- control = Integer.parseInt(matches.group(7));
- number = Integer.parseInt(matches.group(6));
- } catch (NumberFormatException e) {
- return false;
- }
-
- // The format passed to Luhn method is supposed to be YYmmDDNNN
- // Hence all numbers that are less than 10 (or in last case 100) will have leading 0's added.
- int luhn = luhn(String.format("%02d%02d%02d%03d0", year, month, day, number));
- return (luhn == control) && (testDate(year, month, day) || testDate(year, month, day - 60));
- }
-
- /**
- * Validate a Swedish personal identity number.
- *
- * @param value personal identity number to validate, as long.
- * @return True if valid.
- */
- public static boolean valid(long value) {
- return valid(Long.toString(value));
- }
-
- private static int luhn(String value) {
- // Luhn/mod10 algorithm. Used to calculate a checksum from the
- // passed value. The checksum is returned and tested against the control number
- // in the personal identity number to make sure that it is a valid number.
-
- int temp;
- int sum = 0;
-
- for (int i = 0; i < value.length(); i++) {
- temp = Character.getNumericValue(value.charAt(i));
- temp *= 2 - (i % 2);
- if (temp > 9)
- temp -= 9;
-
- sum += temp;
- }
-
- return (int)(Math.ceil((double)sum / 10.0) * 10.0 - (double)sum);
- }
-
- private static boolean testDate(int year, int month, int day) {
- try {
- DateFormat df = new SimpleDateFormat("yy-MM-dd");
- df.setLenient(false);
- df.parse(String.format("%02d-%02d-%02d", year, month, day));
- return true;
- } catch (Exception ex) {
- return false;
- }
- }
-
-}
diff --git a/src/main/java/dev/personnummer/Options.java b/src/main/java/dev/personnummer/Options.java
new file mode 100644
index 0000000..7d8fef1
--- /dev/null
+++ b/src/main/java/dev/personnummer/Options.java
@@ -0,0 +1,12 @@
+package dev.personnummer;
+
+public class Options {
+ public Options(boolean allowCoordinationNumber) {
+ this.allowCoordinationNumber = allowCoordinationNumber;
+ }
+
+ public Options() {
+ }
+
+ boolean allowCoordinationNumber = true;
+}
diff --git a/src/main/java/dev/personnummer/Personnummer.java b/src/main/java/dev/personnummer/Personnummer.java
new file mode 100644
index 0000000..cc638ec
--- /dev/null
+++ b/src/main/java/dev/personnummer/Personnummer.java
@@ -0,0 +1,222 @@
+package dev.personnummer;
+
+import java.time.LocalDate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Class used to validate Swedish personal identity numbers.
+ *
+ * @author Johannes Tegnér
+ */
+public final class Personnummer {
+ private static final Pattern regexPattern;
+
+ static {
+ regexPattern = Pattern.compile("^(\\d{2})?(\\d{2})(\\d{2})(\\d{2})([-|+]?)?((?!000)\\d{3})(\\d?)$");
+ }
+
+ /**
+ * Create a new Personnummber object from a string.
+ * In case options is not passed, they will default to accept any personal and coordination numbers.
+ *
+ * @param personnummer Personal identity number as a string to create the object from.
+ * @param options Options to use when creating the object.
+ * @throws PersonnummerException On parse error.
+ */
+ public static Personnummer parse(String personnummer, Options options) throws PersonnummerException {
+ return new Personnummer(personnummer, options);
+ }
+
+ /**
+ * Create a new Personnummber object from a string.
+ * In case options is not passed, they will default to accept any personal and coordination numbers.
+ *
+ * @param personnummer Personal identity number as a string to create the object from.
+ * @throws PersonnummerException On parse error.
+ */
+ public static Personnummer parse(String personnummer) throws PersonnummerException {
+ return parse(personnummer, new Options());
+ }
+
+
+ private final int realDay;
+ private final String fullYear;
+ private final String century;
+ private final String year;
+ private final String month;
+ private final String day;
+ private final String numbers;
+ private final String controlNumber;
+ private final boolean isMale;
+ private final boolean isFemale;
+
+ public Boolean isMale() {
+ return this.isMale;
+ }
+
+ public Boolean isFemale() {
+ return this.isFemale;
+ }
+
+ public String separator() {
+ return this.getAge() >= 100 ? "+" : "-";
+ }
+
+ public String getFullYear() {
+ return fullYear;
+ }
+
+ public String getCentury() {
+ return century;
+ }
+
+ public String getYear() {
+ return year;
+ }
+
+ public String getMonth() {
+ return month;
+ }
+
+ public String getDay() {
+ return day;
+ }
+
+ public String getNumbers() {
+ return numbers;
+ }
+
+ public String getControlNumber() {
+ return controlNumber;
+ }
+
+ public int getAge() {
+ return (LocalDate.of(Integer.parseInt(this.fullYear), Integer.parseInt(this.month), this.realDay).until(LocalDate.now())).getYears();
+ }
+
+ /**
+ * Create a new Personnummber object from a string.
+ * In case options is not passed, they will default to accept any personal and coordination numbers.
+ *
+ * @param personnummer Personal identity number as a string to create the object from.
+ * @param options Options to use when creating the object.
+ * @throws PersonnummerException On parse error.
+ */
+ public Personnummer(String personnummer, Options options) throws PersonnummerException {
+ if (personnummer == null) {
+ throw new PersonnummerException("Failed to parse personal identity number. Invalid input.");
+ }
+
+ Matcher matches = regexPattern.matcher(personnummer);
+ if (!matches.find()) {
+ throw new PersonnummerException("Failed to parse personal identity number. Invalid input.");
+ }
+
+ String century;
+ String decade = matches.group(2);
+ if (matches.group(1) != null && !matches.group(1).isEmpty()) {
+ century = matches.group(1);
+ } else {
+ int born = LocalDate.now().getYear() - Integer.parseInt(decade);
+ if (!matches.group(5).isEmpty() && matches.group(5).equals("+")) {
+ born -= 100;
+ }
+
+ century = Integer.toString(born).substring(0, 2);
+ }
+
+ int day = Integer.parseInt(matches.group(4));
+ if (options.allowCoordinationNumber) {
+ day = day > 60 ? day - 60 : day;
+ } else if(day > 60) {
+ throw new PersonnummerException("Invalid personal identity number.");
+ }
+
+ this.realDay = day;
+ this.century = century;
+ this.year = decade;
+ this.fullYear = century + decade;
+ this.month = matches.group(3);
+ this.day = matches.group(4);
+ this.numbers = matches.group(6) + matches.group(7);
+ this.controlNumber = matches.group(7);
+
+ this.isMale = Integer.parseInt(Character.toString(this.numbers.charAt(2))) % 2 == 1;
+ this.isFemale = !this.isMale;
+
+ // The format passed to Luhn method is supposed to be YYmmDDNNN
+ // Hence all numbers that are less than 10 (or in last case 100) will have leading 0's added.
+ if (luhn(String.format("%s%s%s%s", this.year, this.month, this.day, matches.group(6))) != Integer.parseInt(this.controlNumber)) {
+ throw new PersonnummerException("Invalid personal identity number.");
+ }
+ }
+
+ /**
+ * Create a new Personnummber object from a string.
+ * In case options is not passed, they will default to accept any personal and coordination numbers.
+ *
+ * @param personnummer Personal identity number as a string to create the object from.
+ * @throws PersonnummerException On parse error.
+ */
+ public Personnummer(String personnummer) throws PersonnummerException {
+ this(personnummer, new Options());
+ }
+
+ /**
+ * Format the personal identity number into a valid string (YYMMDD-/+XXXX)
+ * If longFormat is true, it will include the century (YYYYMMDD-/+XXXX)
+ *
+ * @return Formatted personal identity number.
+ */
+ public String format() {
+ return format(false);
+ }
+
+ /**
+ * Format the personal identity number into a valid string (YYMMDD-/+XXXX)
+ * If longFormat is true, it will include the century (YYYYMMDD-/+XXXX)
+ *
+ * @param longFormat If century should be included.
+ * @return Formatted personal identity number.
+ */
+ public String format(boolean longFormat) {
+ return (longFormat ? this.fullYear : this.year) + this.month + this.day + (longFormat ? "" : separator()) + numbers;
+ }
+
+ /**
+ * Validate a Swedish personal identity number.
+ *
+ * @param personnummer personal identity number to validate, as string.
+ * @return True if valid.
+ */
+ public static boolean valid(String personnummer) {
+ try {
+ parse(personnummer);
+ return true;
+ } catch (PersonnummerException ex) {
+ return false;
+ }
+ }
+
+ private static int luhn(String value) {
+ // Luhn/mod10 algorithm. Used to calculate a checksum from the
+ // passed value. The checksum is returned and tested against the control number
+ // in the personal identity number to make sure that it is a valid number.
+
+ int temp;
+ int sum = 0;
+
+ for (int i = 0; i < value.length(); i++) {
+ temp = Character.getNumericValue(value.charAt(i));
+ temp *= 2 - (i % 2);
+ if (temp > 9)
+ temp -= 9;
+
+ sum += temp;
+ }
+
+ return (int)(Math.ceil((double)sum / 10.0) * 10.0 - (double)sum);
+ }
+
+}
diff --git a/src/main/java/dev/personnummer/PersonnummerException.java b/src/main/java/dev/personnummer/PersonnummerException.java
new file mode 100644
index 0000000..be13965
--- /dev/null
+++ b/src/main/java/dev/personnummer/PersonnummerException.java
@@ -0,0 +1,7 @@
+package dev.personnummer;
+
+public class PersonnummerException extends Exception {
+ PersonnummerException(String message) {
+ super(message);
+ }
+}
diff --git a/src/test/java/DataProvider.java b/src/test/java/DataProvider.java
new file mode 100644
index 0000000..65302e0
--- /dev/null
+++ b/src/test/java/DataProvider.java
@@ -0,0 +1,57 @@
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.*;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class DataProvider {
+ private static final List all = new ArrayList<>();
+
+ public static void initialize() throws IOException {
+ InputStream in = new URL("https://raw.githubusercontent.com/personnummer/meta/master/testdata/list.json").openStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(in));
+ String json = "";
+ String line;
+ while ((line = reader.readLine()) != null) {
+ json = json.concat(line);
+ }
+ JSONArray rootObject = new JSONArray(json);
+ for (int i = 0; i < rootObject.length(); i++) {
+ JSONObject current = rootObject.getJSONObject(i);
+ all.add(new PersonnummerData(
+ current.getLong("integer"),
+ current.getString("long_format"),
+ current.getString("short_format"),
+ current.getString("separated_format"),
+ current.getString("separated_long"),
+ current.getBoolean("valid"),
+ current.getString("type"),
+ current.getBoolean("isMale"),
+ current.getBoolean("isFemale")
+ ));
+ }
+ }
+
+ public static List getCoordinationNumbers() {
+ return all.stream().filter(o -> !o.type.equals("ssn")).collect(Collectors.toList());
+ }
+ public static List getPersonnummer() {
+ return all.stream().filter(o -> o.type.equals("ssn")).collect(Collectors.toList());
+ }
+ public static List getInvalidCoordinationNumbers() {
+ return getCoordinationNumbers().stream().filter(o -> !o.valid).collect(Collectors.toList());
+ }
+ public static List getInvalidPersonnummer() {
+ return getPersonnummer().stream().filter(o -> !o.valid).collect(Collectors.toList());
+ }
+ public static List getValidCoordinationNumbers() {
+ return getCoordinationNumbers().stream().filter(o -> o.valid).collect(Collectors.toList());
+ }
+ public static List getValidPersonnummer() {
+ return getPersonnummer().stream().filter(o -> o.valid).collect(Collectors.toList());
+ }
+
+}
diff --git a/src/test/java/PersonnummerData.java b/src/test/java/PersonnummerData.java
new file mode 100644
index 0000000..b86e6b3
--- /dev/null
+++ b/src/test/java/PersonnummerData.java
@@ -0,0 +1,25 @@
+public class PersonnummerData {
+
+ public PersonnummerData(Long integer, String longFormat, String shortFormat, String separatedFormat, String separatedLong, Boolean valid, String type, Boolean isMale, Boolean isFemale) {
+ this.integer = integer;
+ this.longFormat = longFormat;
+ this.shortFormat = shortFormat;
+ this.separatedFormat = separatedFormat;
+ this.separatedLong = separatedLong;
+ this.valid = valid;
+ this.type = type;
+ this.isMale = isMale;
+ this.isFemale = isFemale;
+ }
+
+ public Long integer;
+ public String longFormat;
+ public String shortFormat;
+ public String separatedFormat;
+ public String separatedLong;
+ public Boolean valid;
+ public String type;
+ public Boolean isMale;
+ public boolean isFemale;
+
+}
diff --git a/src/test/java/PersonnummerTest.java b/src/test/java/PersonnummerTest.java
index 29c39d1..4cd4598 100644
--- a/src/test/java/PersonnummerTest.java
+++ b/src/test/java/PersonnummerTest.java
@@ -1,136 +1,178 @@
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.util.ArrayList;
-import java.util.List;
-import org.json.*;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
-public class PersonnummerTest {
- private static Boolean fileLoaded = false;
+import dev.personnummer.*;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import static org.junit.jupiter.api.Assertions.*;
- private static List validSsnInt = new ArrayList<>();
- private static List validSsnString = new ArrayList<>();
- private static List invalidSsnInt = new ArrayList<>();
- private static List invalidSsnString = new ArrayList<>();
- private static List validConInt = new ArrayList<>();
- private static List validConString = new ArrayList<>();
- private static List invalidConInt = new ArrayList<>();
- private static List invalidConString = new ArrayList<>();
+public class PersonnummerTest {
- @AfterClass
- public static void deleteTestData() throws IOException {
- Files.delete(Paths.get("temp.json"));
+ @BeforeAll
+ public static void setup() throws IOException {
+ DataProvider.initialize();
}
- @BeforeClass
- public static void loadTestData() throws IOException {
- if (fileLoaded) {
- return;
- }
+ @ParameterizedTest
+ @MethodSource("DataProvider#getValidPersonnummer")
+ public void testConstructor(PersonnummerData ssn) {
+ assertDoesNotThrow(() -> new Personnummer(ssn.longFormat, new Options(false)));
+ assertDoesNotThrow(() -> new Personnummer(ssn.shortFormat, new Options(false)));
+ assertDoesNotThrow(() -> new Personnummer(ssn.separatedFormat, new Options(false)));
+ assertDoesNotThrow(() -> new Personnummer(ssn.separatedFormat, new Options(false)));
+ }
- if (!Files.exists(Paths.get("temp.json"))) {
- InputStream in = new URL("https://raw.githubusercontent.com/personnummer/meta/master/testdata/structured.json").openStream();
- Files.copy(in, Paths.get("temp.json"), StandardCopyOption.REPLACE_EXISTING);
- fileLoaded = true;
- }
+ @ParameterizedTest
+ @MethodSource("DataProvider#getValidCoordinationNumbers")
+ public void testConstructorCoord(PersonnummerData ssn) {
+ assertDoesNotThrow(() -> new Personnummer(ssn.longFormat, new Options(true)));
+ assertDoesNotThrow(() -> new Personnummer(ssn.shortFormat, new Options(true)));
+ assertDoesNotThrow(() -> new Personnummer(ssn.separatedFormat, new Options(true)));
+ assertDoesNotThrow(() -> new Personnummer(ssn.separatedFormat, new Options(true)));
+ }
- String jsonString = new String(Files.readAllBytes(Paths.get("temp.json")));
- JSONObject json = new JSONObject(jsonString);
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getInvalidPersonnummer", "DataProvider#getValidCoordinationNumbers"})
+ public void testConstructorInvalid(PersonnummerData ssn) {
+ assertThrows(PersonnummerException.class, () -> new Personnummer(ssn.longFormat, new Options(false)));
+ assertThrows(PersonnummerException.class, () -> new Personnummer(ssn.shortFormat, new Options(false)));
+ assertThrows(PersonnummerException.class, () -> new Personnummer(ssn.separatedFormat, new Options(false)));
+ assertThrows(PersonnummerException.class, () -> new Personnummer(ssn.separatedFormat, new Options(false)));
+ }
- JSONObject ssn = json.getJSONObject("ssn");
- JSONObject con = json.getJSONObject("con");
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getInvalidCoordinationNumbers"})
+ public void testConstructorCoordInvalid(PersonnummerData ssn) {
+ assertThrows(PersonnummerException.class, () -> new Personnummer(ssn.longFormat, new Options(true)));
+ assertThrows(PersonnummerException.class, () -> new Personnummer(ssn.shortFormat, new Options(true)));
+ assertThrows(PersonnummerException.class, () -> new Personnummer(ssn.separatedFormat, new Options(true)));
+ assertThrows(PersonnummerException.class, () -> new Personnummer(ssn.separatedFormat, new Options(true)));
+ }
- validSsnInt = getIntList(ssn, "integer", "valid");
- invalidSsnInt = getIntList(ssn, "integer", "invalid");
- validSsnString = getStringList(ssn, "string", "valid");
- invalidSsnString = getStringList(ssn, "string", "invalid");
+ @ParameterizedTest
+ @MethodSource("DataProvider#getValidPersonnummer")
+ public void testParse(PersonnummerData ssn) {
+ assertDoesNotThrow(() -> Personnummer.parse(ssn.longFormat, new Options(false)));
+ assertDoesNotThrow(() -> Personnummer.parse(ssn.shortFormat, new Options(false)));
+ assertDoesNotThrow(() -> Personnummer.parse(ssn.separatedFormat, new Options(false)));
+ assertDoesNotThrow(() -> Personnummer.parse(ssn.separatedFormat, new Options(false)));
+ }
- validConInt = getIntList(con, "integer", "valid");
- invalidConInt = getIntList(con, "integer", "invalid");
- validConString = getStringList(con, "string", "valid");
- invalidConString = getStringList(con, "string", "invalid");
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getValidCoordinationNumbers", "DataProvider#getValidPersonnummer"})
+ public void testParseCoord(PersonnummerData ssn) {
+ assertDoesNotThrow(() -> Personnummer.parse(ssn.longFormat, new Options(true)));
+ assertDoesNotThrow(() -> Personnummer.parse(ssn.shortFormat, new Options(true)));
+ assertDoesNotThrow(() -> Personnummer.parse(ssn.separatedFormat, new Options(true)));
+ assertDoesNotThrow(() -> Personnummer.parse(ssn.separatedFormat, new Options(true)));
}
- private static ArrayList getStringList(JSONObject root, String dataType, String valid) {
- JSONArray arr = root.getJSONObject(dataType).getJSONArray(valid);
- ArrayList result = new ArrayList<>();
- for (int i=0; i Personnummer.parse(ssn.longFormat, new Options(false)));
+ assertThrows(PersonnummerException.class, () -> Personnummer.parse(ssn.shortFormat, new Options(false)));
+ assertThrows(PersonnummerException.class, () -> Personnummer.parse(ssn.separatedFormat, new Options(false)));
+ assertThrows(PersonnummerException.class, () -> Personnummer.parse(ssn.separatedFormat, new Options(false)));
}
- private static ArrayList getIntList(JSONObject root, String dataType, String valid) {
- JSONArray arr = root.getJSONObject(dataType).getJSONArray(valid);
- ArrayList result = new ArrayList<>();
- for (int i=0; i Personnummer.parse(ssn.longFormat, new Options(true)));
+ assertThrows(PersonnummerException.class, () -> Personnummer.parse(ssn.shortFormat, new Options(true)));
+ assertThrows(PersonnummerException.class, () -> Personnummer.parse(ssn.separatedFormat, new Options(true)));
+ assertThrows(PersonnummerException.class, () -> Personnummer.parse(ssn.separatedFormat, new Options(true)));
}
- @Test
- public void testPersonnNummerWithInvalidIntegerValues() {
- for (Long ssn: invalidSsnInt) {
- assertFalse(Personnummer.valid(ssn));
- }
+
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getValidPersonnummer"})
+ public void testAge(PersonnummerData ssn) throws PersonnummerException {
+ LocalDate date = LocalDate.parse(ssn.longFormat.substring(0, ssn.longFormat.length() - 4), DateTimeFormatter.ofPattern("yyyyMMdd"));
+ int years = (date.until(LocalDate.now())).getYears();
+
+ assertEquals(years, Personnummer.parse(ssn.separatedLong, new Options(false)).getAge());
+ assertEquals(years, Personnummer.parse(ssn.separatedFormat, new Options(false)).getAge());
+ assertEquals(years, Personnummer.parse(ssn.longFormat, new Options(false)).getAge());
+ assertEquals(years > 99 ? years - 100 : years, Personnummer.parse(ssn.shortFormat, new Options(false)).getAge());
}
- @Test
- public void testCoordinationNummerWithInvalidIntegerValues() {
- for (Long ssn: invalidConInt) {
- assertFalse(Personnummer.valid(ssn));
- }
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getValidCoordinationNumbers"})
+ public void testAgeCn(PersonnummerData ssn) throws PersonnummerException {
+ String strDay = ssn.longFormat.substring(ssn.longFormat.length() - 6, ssn.longFormat.length() - 4);
+ int day = Integer.parseInt(strDay) - 60;
+ strDay = day < 10 ? "0" + Integer.toString(day) : Integer.toString(day);
+
+ LocalDate date = LocalDate.parse(ssn.longFormat.substring(0, ssn.longFormat.length() - 6) + strDay, DateTimeFormatter.ofPattern("yyyyMMdd"));
+ int years = (date.until(LocalDate.now())).getYears();
+
+ assertEquals(years, Personnummer.parse(ssn.separatedLong, new Options(true)).getAge());
+ assertEquals(years, Personnummer.parse(ssn.separatedFormat, new Options(true)).getAge());
+ assertEquals(years, Personnummer.parse(ssn.longFormat, new Options(true)).getAge());
+ assertEquals(years > 99 ? years - 100 : years, Personnummer.parse(ssn.shortFormat, new Options(true)).getAge());
}
- @Test
- public void testPersonnNummerWithInvalidStringValues() {
- for (String ssn: invalidSsnString) {
- assertFalse(Personnummer.valid(ssn));
- }
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getValidPersonnummer", "DataProvider#getValidCoordinationNumbers"})
+ public void testFormat(PersonnummerData ssn) throws PersonnummerException {
+ assertEquals(ssn.separatedFormat, Personnummer.parse(ssn.separatedLong, new Options(true)).format());
+ assertEquals(ssn.separatedFormat, Personnummer.parse(ssn.separatedFormat, new Options(true)).format());
+ assertEquals(ssn.separatedFormat, Personnummer.parse(ssn.longFormat, new Options(true)).format());
}
- @Test
- public void testCoordinationNummerWithInvalidStringValues() {
- for (String ssn: invalidConString) {
- assertFalse(Personnummer.valid(ssn));
- }
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getValidPersonnummer", "DataProvider#getValidCoordinationNumbers"})
+ public void testFormatLong(PersonnummerData ssn) throws PersonnummerException {
+ assertEquals(ssn.longFormat, Personnummer.parse(ssn.separatedLong, new Options(true)).format(true));
+ assertEquals(ssn.longFormat, Personnummer.parse(ssn.separatedFormat, new Options(true)).format(true));
+ assertEquals(ssn.longFormat, Personnummer.parse(ssn.longFormat, new Options(true)).format(true));
}
- @Test
- public void testPersonnNummerWithValidIntegerValues() {
- for (Long ssn: validSsnInt) {
- assertTrue(Personnummer.valid(ssn));
- }
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getValidPersonnummer", "DataProvider#getValidCoordinationNumbers"})
+ public void testValid(PersonnummerData ssn) {
+ assertTrue(Personnummer.valid(ssn.longFormat));
+ assertTrue(Personnummer.valid(ssn.separatedLong));
+ assertTrue(Personnummer.valid(ssn.separatedFormat));
+ assertTrue(Personnummer.valid(ssn.shortFormat));
}
- @Test
- public void testCoordinationNummerVnvalidIntegerValues() {
- for (Long ssn: validConInt) {
- assertTrue(Personnummer.valid(ssn));
- }
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getInvalidPersonnummer", "DataProvider#getInvalidCoordinationNumbers"})
+ public void testValidInvalid(PersonnummerData ssn) {
+ assertFalse(Personnummer.valid(ssn.longFormat));
+ assertFalse(Personnummer.valid(ssn.separatedLong));
+ assertFalse(Personnummer.valid(ssn.separatedFormat));
+ assertFalse(Personnummer.valid(ssn.shortFormat));
}
- @Test
- public void testPersonnNummerWithValidStringValues() {
- for (String ssn: validSsnString) {
- assertTrue(Personnummer.valid(ssn));
- }
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getValidPersonnummer", "DataProvider#getValidCoordinationNumbers"})
+ public void testMaleFemale(PersonnummerData ssn) throws PersonnummerException {
+ assertEquals(ssn.isMale, Personnummer.parse(ssn.longFormat, new Options(true)).isMale());
+ assertEquals(ssn.isMale, Personnummer.parse(ssn.separatedLong, new Options(true)).isMale());
+ assertEquals(ssn.isMale, Personnummer.parse(ssn.separatedFormat, new Options(true)).isMale());
+ assertEquals(ssn.isMale, Personnummer.parse(ssn.shortFormat, new Options(true)).isMale());
+
+ assertEquals(ssn.isFemale, Personnummer.parse(ssn.longFormat, new Options(true)).isFemale());
+ assertEquals(ssn.isFemale, Personnummer.parse(ssn.separatedLong, new Options(true)).isFemale());
+ assertEquals(ssn.isFemale, Personnummer.parse(ssn.separatedFormat, new Options(true)).isFemale());
+ assertEquals(ssn.isFemale, Personnummer.parse(ssn.shortFormat, new Options(true)).isFemale());
}
- @Test
- public void testCoordinationNummerWithValidStringValues() {
- for (String ssn: validConString) {
- assertTrue(Personnummer.valid(ssn));
- }
+ @ParameterizedTest
+ @MethodSource({"DataProvider#getValidPersonnummer", "DataProvider#getValidCoordinationNumbers"})
+ public void testSeparator(PersonnummerData ssn) throws PersonnummerException {
+ String sep = ssn.separatedFormat.contains("+") ? "+" : "-";
+ assertEquals(sep, Personnummer.parse(ssn.longFormat, new Options(true)).separator());
+ assertEquals(sep, Personnummer.parse(ssn.separatedLong, new Options(true)).separator());
+ assertEquals(sep, Personnummer.parse(ssn.separatedFormat, new Options(true)).separator());
+ // Getting the separator from a short formatted none-separated person number is not actually possible if it is intended to be a +.
}
}