Skip to content

Commit

Permalink
Merge pull request #1316 from datafaker-net/feature/id-number-with-bi…
Browse files Browse the repository at this point in the history
…rthday-and-gender

generate ID Number together with birthday and gender
  • Loading branch information
kingthorin authored Aug 10, 2024
2 parents 9107dbd + 2af34fa commit 28631b8
Show file tree
Hide file tree
Showing 26 changed files with 550 additions and 154 deletions.
22 changes: 15 additions & 7 deletions src/main/java/net/datafaker/idnumbers/AlbanianIdNumber.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package net.datafaker.idnumbers;

import net.datafaker.providers.base.BaseProviders;
import net.datafaker.providers.base.IdNumber.IdNumberRequest;
import net.datafaker.providers.base.PersonIdNumber;
import net.datafaker.providers.base.PersonIdNumber.Gender;

import java.time.LocalDate;

import static net.datafaker.idnumbers.Utils.gender;
import static net.datafaker.idnumbers.Utils.birthday;
import static net.datafaker.providers.base.PersonIdNumber.Gender.FEMALE;

/**
* The Albanian Identity Number is a unique personal identification number of 10 characters in the format YYMMDDSSSC
*/
Expand All @@ -24,19 +31,20 @@ public String generateInvalid(BaseProviders faker) {
}

@Override
public String generateValid(BaseProviders faker) {
LocalDate birthDate = faker.timeAndDate().birthday(0, 200);
boolean female = faker.bool().bool();
String basePart = yy(birthDate.getYear()) + mm(birthDate.getMonthValue(), female) + dd(birthDate.getDayOfMonth()) + sss(faker);
return basePart + checksum(basePart);
public PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request) {
LocalDate birthDate = birthday(faker, request);
Gender gender = gender(faker, request);
String basePart = yy(birthDate.getYear()) + mm(birthDate.getMonthValue(), gender) + dd(birthDate.getDayOfMonth()) + sss(faker);
String idNumber = basePart + checksum(basePart);
return new PersonIdNumber(idNumber, birthDate, gender);
}

String yy(int year) {
return FIRST_CHAR.charAt((year - 1800) / 10) + String.valueOf(year % 10);
}

String mm(int month, boolean female) {
return String.format("%02d", (female ? 50 : 0) + month);
String mm(int month, Gender gender) {
return String.format("%02d", (gender == FEMALE ? 50 : 0) + month);
}

String dd(int dayOfMonth) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/net/datafaker/idnumbers/AmericanIdNumber.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package net.datafaker.idnumbers;

import net.datafaker.providers.base.BaseProviders;
import net.datafaker.providers.base.IdNumber;
import net.datafaker.providers.base.PersonIdNumber;

import java.util.List;
import java.util.regex.Pattern;

import static net.datafaker.idnumbers.Utils.gender;
import static net.datafaker.idnumbers.Utils.birthday;

public class AmericanIdNumber implements IdNumberGenerator {
@Override
public String countryCode() {
Expand Down Expand Up @@ -36,6 +41,11 @@ public String generateValid(BaseProviders f) {
return isValid ? ssn : generateValid(f);
}

@Override
public PersonIdNumber generateValid(BaseProviders faker, IdNumber.IdNumberRequest request) {
return new PersonIdNumber(generateValid(faker), birthday(faker, request), gender(faker, request));
}

@Override
public String generateInvalid(BaseProviders faker) {
return faker.regexify(faker.options().nextElement(INVALID_SSNS));
Expand Down
31 changes: 21 additions & 10 deletions src/main/java/net/datafaker/idnumbers/BulgarianIdNumber.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
package net.datafaker.idnumbers;

import net.datafaker.providers.base.BaseProviders;
import net.datafaker.providers.base.IdNumber.IdNumberRequest;
import net.datafaker.providers.base.PersonIdNumber;
import net.datafaker.providers.base.PersonIdNumber.Gender;

import java.time.LocalDate;

import static net.datafaker.idnumbers.Utils.gender;
import static net.datafaker.idnumbers.Utils.birthday;
import static net.datafaker.idnumbers.Utils.randomGender;

/**
* <a href="https://en.wikipedia.org/wiki/Unique_citizenship_number">Specification</a>
*/
Expand All @@ -18,21 +25,22 @@ public String countryCode() {
}

@Override
public String generateValid(BaseProviders faker) {
String basePart = basePart(faker);
return basePart + checksum(basePart);
public PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request) {
LocalDate birthday = birthday(faker, request);
Gender gender = gender(faker, request);
String basePart = basePart(faker, birthday, gender);
String idNumber = basePart + checksum(basePart);
return new PersonIdNumber(idNumber, birthday, gender);
}

@Override
public String generateInvalid(BaseProviders faker) {
String basePart = basePart(faker);
String basePart = basePart(faker, faker.timeAndDate().birthday(), randomGender(faker));
return basePart + (checksum(basePart) + 1) % 10;
}

private String basePart(BaseProviders faker) {
LocalDate birthDate = faker.timeAndDate().birthday(0, 200);
boolean female = faker.bool().bool();
return yy(birthDate) + mm(birthDate) + dd(birthDate) + order(faker, female);
private String basePart(BaseProviders faker, LocalDate birthDate, Gender gender) {
return yy(birthDate) + mm(birthDate) + dd(birthDate) + order(faker, gender);
}

private String yy(LocalDate birthDate) {
Expand All @@ -49,8 +57,11 @@ private String dd(LocalDate birthDate) {
return "%02d".formatted(birthDate.getDayOfMonth());
}

private String order(BaseProviders faker, boolean female) {
int[] availableLastDigits = female ? ODD_DIGITS : EVEN_DIGITS;
private String order(BaseProviders faker, Gender gender) {
int[] availableLastDigits = switch (gender) {
case FEMALE -> ODD_DIGITS;
case MALE -> EVEN_DIGITS;
};
int lastDigit = availableLastDigits[faker.number().numberBetween(0, 5)];
return faker.number().digits(2) + lastDigit;
}
Expand Down
37 changes: 16 additions & 21 deletions src/main/java/net/datafaker/idnumbers/ChineseIdNumber.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
package net.datafaker.idnumbers;

import net.datafaker.providers.base.BaseProviders;
import net.datafaker.providers.base.IdNumber.IdNumberRequest;
import net.datafaker.providers.base.PersonIdNumber;
import net.datafaker.service.RandomService;

import java.time.LocalDate;

import static net.datafaker.idnumbers.Utils.gender;
import static net.datafaker.idnumbers.Utils.birthday;


/**
* This class is used for generating Chinese ID numbers
Expand Down Expand Up @@ -32,7 +39,8 @@ public String getValidSsn(BaseProviders faker) {
* @return a Chinese ID number string
*/
@Override
public String generateValid(BaseProviders faker) {
public PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request) {
LocalDate birthday = birthday(faker, request);
RandomService rand = faker.random();
String loc = faker.options().option(LOCATIONS);
final int dayLength = 8;
Expand All @@ -42,7 +50,7 @@ public String generateValid(BaseProviders faker) {
res[i] = loc.charAt(i);
}

generateDay(rand, 1930, 1, 1, 2030, 1, 12, res, locLength);
fillBirthday(res, locLength, birthday);
res[locLength + dayLength] = (char)('0' + rand.nextInt(10));
res[locLength + dayLength + 1] = (char)('0' + rand.nextInt(10));
res[locLength + dayLength + 2] = (char)('0' + rand.nextInt(10));
Expand All @@ -65,27 +73,14 @@ public String generateValid(BaseProviders faker) {
count += (res[15] - '0') * 4;
count += (res[16] - '0') * 2;
count %= 11;
if (count == 10) {
return String.valueOf(res) + "X";
} else {
return String.valueOf(res) + count;
}
String idNumber = count == 10 ? String.valueOf(res) + "X" : String.valueOf(res) + count;
return new PersonIdNumber(idNumber, birthday, gender(faker, request));
}

private void generateDay(RandomService rand, int yearStart, int monthStart, int dayStart, int yearEnd, int monthEnd, int dayEnd, char[] res, int offset) {
final int year = rand.nextInt(yearStart, yearEnd);
final int month = rand.nextInt(monthStart, monthEnd);
final int lastDay;
if (month == 2) {
lastDay =
(year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) ?
29 : 28;
} else if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) {
lastDay = 31;
} else {
lastDay = 30;
}
int day = rand.nextInt(dayStart, Math.min(lastDay, dayEnd));
private void fillBirthday(char[] res, int offset, LocalDate birthday) {
int year = birthday.getYear();
int month = birthday.getMonthValue();
int day = birthday.getDayOfMonth();
res[offset] = (char)('0' + year / 1000);
res[offset + 1] = (char)('0' + (year % 1000) / 100);
res[offset + 2] = (char)('0' + (year % 100) / 10);
Expand Down
38 changes: 29 additions & 9 deletions src/main/java/net/datafaker/idnumbers/EstonianIdNumber.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package net.datafaker.idnumbers;

import net.datafaker.providers.base.BaseProviders;
import net.datafaker.providers.base.IdNumber.IdNumberRequest;
import net.datafaker.providers.base.PersonIdNumber;
import net.datafaker.providers.base.PersonIdNumber.Gender;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import static net.datafaker.idnumbers.Utils.gender;
import static net.datafaker.idnumbers.Utils.birthday;
import static net.datafaker.idnumbers.Utils.randomGender;

/**
* Estonian personal identification number ("Isikukood" in estonian)
* <p>
Expand All @@ -25,25 +32,38 @@ public String countryCode() {

@Override
public String generateInvalid(final BaseProviders faker) {
String digits = basePart(faker);
LocalDate birthday = faker.timeAndDate().birthday();
String digits = basePart(faker, birthday, randomGender(faker));
return digits + (checksum(digits) + 1) % 10;
}

@Override
public String generateValid(final BaseProviders faker) {
String digits = basePart(faker);
return digits + checksum(digits);
public PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request) {
LocalDate birthday = birthday(faker, request);
Gender gender = gender(faker, request);
String digits = basePart(faker, birthday, gender);
String idNumber = digits + checksum(digits);
return new PersonIdNumber(idNumber, birthday, gender);
}

private String basePart(BaseProviders faker) {
LocalDate birthday = faker.timeAndDate().birthday(0, 100);
return firstDigit(faker) +
private String basePart(BaseProviders faker, LocalDate birthday, Gender gender) {
return firstDigit(birthday.getYear(), gender) +
BIRTHDAY_FORMAT.format(birthday) +
faker.number().digits(3);
}

private int firstDigit(BaseProviders faker) {
return faker.random().nextInt(1, 6);
static int firstDigit(int birthYear, Gender gender) {
int digit = switch (birthYear / 100) {
case 18 -> 1;
case 19 -> 3;
case 20 -> 5;
case 21 -> 7;
default -> throw new IllegalStateException("Too far in future: " + birthYear);
};
return switch (gender) {
case FEMALE -> digit + 1;
case MALE -> digit;
};
}

static int checksum(String numbers) {
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/net/datafaker/idnumbers/GeorgianIdNumber.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package net.datafaker.idnumbers;

import net.datafaker.providers.base.BaseProviders;
import net.datafaker.providers.base.IdNumber;
import net.datafaker.providers.base.PersonIdNumber;

import static net.datafaker.idnumbers.Utils.birthday;
import static net.datafaker.idnumbers.Utils.gender;

/**
* Generates ID numbers for Georgian citizens and Residents
Expand All @@ -16,6 +21,15 @@ public String generateValid(BaseProviders faker) {
return faker.numerify("###########");
}

@Override
public PersonIdNumber generateValid(BaseProviders faker, IdNumber.IdNumberRequest request) {
return new PersonIdNumber(
generateValid(faker),
birthday(faker, request),
gender(faker, request)
);
}

@Override
public String generateInvalid(BaseProviders faker) {
return faker.numerify("###########42");
Expand Down
18 changes: 14 additions & 4 deletions src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package net.datafaker.idnumbers;

import net.datafaker.providers.base.BaseProviders;
import net.datafaker.providers.base.IdNumber.IdNumberRequest;
import net.datafaker.providers.base.PersonIdNumber;

import java.time.format.DateTimeFormatter;
import static net.datafaker.providers.base.IdNumber.GenderRequest.ANY;

public interface IdNumberGenerator {
DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd");

/**
* ISO-2 code of the country this generator provides ID numbers for
*
Expand All @@ -17,10 +17,20 @@ public interface IdNumberGenerator {
/**
* Generates a valid ID number for given country (a.k.a. "SSN", "Personal code" etc.)
*/
String generateValid(BaseProviders faker);
default String generateValid(BaseProviders faker) {
return generateValid(faker, new IdNumberRequest(18, 65, ANY)).idNumber();
}

/**
* Generates an invalid ID number for given country (a.k.a. "SSN", "Personal code" etc.)
*/
String generateInvalid(BaseProviders faker);

/**
* Generates a valid ID number for given country corresponding to given criterias (age range, gender etc.)
*
* @return PersonIdNumber containing a valid combination of ID, Birthday and Gender.
* In countries where ID number doesn't contain gender and/or birthday, the latter values are just random.
*/
PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request);
}
32 changes: 22 additions & 10 deletions src/main/java/net/datafaker/idnumbers/MacedonianIdNumber.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package net.datafaker.idnumbers;

import net.datafaker.providers.base.BaseProviders;
import net.datafaker.providers.base.IdNumber.IdNumberRequest;
import net.datafaker.providers.base.PersonIdNumber;
import net.datafaker.providers.base.PersonIdNumber.Gender;

import java.time.LocalDate;
import java.util.List;

import static net.datafaker.idnumbers.Utils.gender;
import static net.datafaker.idnumbers.Utils.birthday;
import static net.datafaker.idnumbers.Utils.randomGender;

/**
* The Macedonian Identity Number is a unique personal identification number of 13 digits in a form "DD MM YYY RR BBB K"
*
Expand All @@ -20,21 +27,23 @@ public String countryCode() {
}

@Override
public String generateValid(BaseProviders faker) {
String basePart = basePart(faker);
return basePart + checksum(basePart);
public PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request) {
LocalDate birthday = birthday(faker, request);
Gender gender = gender(faker, request);
String basePart = basePart(faker, birthday, gender);
return new PersonIdNumber(basePart + checksum(basePart), birthday, gender);
}

@Override
public String generateInvalid(BaseProviders faker) {
String basePart = basePart(faker);
LocalDate birthday = faker.timeAndDate().birthday();
Gender gender = randomGender(faker);
String basePart = basePart(faker, birthday, gender);
return basePart + (checksum(basePart) + 1) % 10;
}

private String basePart(BaseProviders faker) {
LocalDate bd = faker.timeAndDate().birthday(0, 120);
boolean female = faker.bool().bool();
return dd(bd) + mm(bd) + yyy(bd) + rr(faker) + sss(faker, female);
private String basePart(BaseProviders faker, LocalDate bd, Gender gender) {
return dd(bd) + mm(bd) + yyy(bd) + rr(faker) + sss(faker, gender);
}

private String dd(LocalDate bd) {
Expand Down Expand Up @@ -64,8 +73,11 @@ private String rr(BaseProviders faker) {
* - from 000 to 499 for the male, and
* - from 500 to 999 for the female citizens.
*/
private String sss(BaseProviders faker, boolean female) {
int ordinal = female ? faker.number().numberBetween(500, 1000) : faker.number().numberBetween(0, 500);
private String sss(BaseProviders faker, Gender gender) {
int ordinal = switch (gender) {
case FEMALE -> faker.number().numberBetween(500, 1000);
case MALE -> faker.number().numberBetween(0, 500);
};
return "%03d".formatted(ordinal);
}

Expand Down
Loading

0 comments on commit 28631b8

Please sign in to comment.