diff --git a/src/main/java/net/datafaker/idnumbers/AlbanianIdNumber.java b/src/main/java/net/datafaker/idnumbers/AlbanianIdNumber.java index 60b9d1227..c40a33689 100644 --- a/src/main/java/net/datafaker/idnumbers/AlbanianIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/AlbanianIdNumber.java @@ -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 */ @@ -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) { diff --git a/src/main/java/net/datafaker/idnumbers/AmericanIdNumber.java b/src/main/java/net/datafaker/idnumbers/AmericanIdNumber.java index b6c807392..f65b00086 100644 --- a/src/main/java/net/datafaker/idnumbers/AmericanIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/AmericanIdNumber.java @@ -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() { @@ -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)); diff --git a/src/main/java/net/datafaker/idnumbers/BulgarianIdNumber.java b/src/main/java/net/datafaker/idnumbers/BulgarianIdNumber.java index b06e82c70..d593807a9 100644 --- a/src/main/java/net/datafaker/idnumbers/BulgarianIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/BulgarianIdNumber.java @@ -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; + /** * Specification */ @@ -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) { @@ -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; } diff --git a/src/main/java/net/datafaker/idnumbers/ChineseIdNumber.java b/src/main/java/net/datafaker/idnumbers/ChineseIdNumber.java index 5e56ef78a..547730cb7 100644 --- a/src/main/java/net/datafaker/idnumbers/ChineseIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/ChineseIdNumber.java @@ -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 @@ -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; @@ -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)); @@ -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); diff --git a/src/main/java/net/datafaker/idnumbers/EstonianIdNumber.java b/src/main/java/net/datafaker/idnumbers/EstonianIdNumber.java index d0b7b6453..b48421d91 100644 --- a/src/main/java/net/datafaker/idnumbers/EstonianIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/EstonianIdNumber.java @@ -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) *

@@ -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) { diff --git a/src/main/java/net/datafaker/idnumbers/GeorgianIdNumber.java b/src/main/java/net/datafaker/idnumbers/GeorgianIdNumber.java index e15cb7ec1..3c9384408 100644 --- a/src/main/java/net/datafaker/idnumbers/GeorgianIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/GeorgianIdNumber.java @@ -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 @@ -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"); diff --git a/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java b/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java index e1e218b3c..89a59f73d 100644 --- a/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java +++ b/src/main/java/net/datafaker/idnumbers/IdNumberGenerator.java @@ -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 * @@ -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); } diff --git a/src/main/java/net/datafaker/idnumbers/MacedonianIdNumber.java b/src/main/java/net/datafaker/idnumbers/MacedonianIdNumber.java index 0fde1d2d6..5d1603a02 100644 --- a/src/main/java/net/datafaker/idnumbers/MacedonianIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/MacedonianIdNumber.java @@ -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" * @@ -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) { @@ -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); } diff --git a/src/main/java/net/datafaker/idnumbers/MexicanIdNumber.java b/src/main/java/net/datafaker/idnumbers/MexicanIdNumber.java index f0f0c1960..a07a55426 100644 --- a/src/main/java/net/datafaker/idnumbers/MexicanIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/MexicanIdNumber.java @@ -1,10 +1,16 @@ package net.datafaker.idnumbers; import net.datafaker.providers.base.BaseProviders; +import net.datafaker.providers.base.IdNumber.IdNumberRequest; import net.datafaker.providers.base.Options; +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; + /** * Implementation based on the definition at * https://en.wikipedia.org/wiki/Unique_Population_Registry_Code @@ -43,7 +49,6 @@ public String countryCode() { "OA", "PU", "QT", "QR", "SL", "SI", "SO", "TB", "TM", "TL", "VE", "YU", "ZA", "NE", }; - private static final char[] SEX = {'H', 'M'}; @Deprecated public String get(BaseProviders faker) { @@ -57,24 +62,33 @@ public String get(BaseProviders faker) { * @return A valid MEX CURP. */ @Override - public String generateValid(BaseProviders faker) { - final Options options = faker.options(); - char[] birthDay = getBirthday(faker).toCharArray(); + public PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request) { + Gender gender = gender(faker, request); + LocalDate birthday = birthday(faker, request); + char[] birthDay = formatBirthday(birthday).toCharArray(); final char[] ssn = new char[18]; + final Options options = faker.options(); ssn[0] = options.option(CONSONANT); ssn[1] = options.option(VOWEL); ssn[2] = options.option(CONSONANT); ssn[3] = options.option(CONSONANT); System.arraycopy(birthDay, 0, ssn, 4, 6); - ssn[10] = options.option(SEX); + ssn[10] = genderCharacter(gender); System.arraycopy(options.option(STATES).toCharArray(), 0, ssn, 11, 2); ssn[13] = options.option(VOWEL); ssn[14] = options.option(VOWEL); ssn[15] = options.option(VOWEL); ssn[16] = (birthDay[0] == '1' ? '0' : options.option(CONSONANT)); ssn[17] = String.valueOf(getChecksum(ssn)).charAt(0); - return String.valueOf(ssn); + return new PersonIdNumber(String.valueOf(ssn), birthday, gender); + } + + private char genderCharacter(Gender gender) { + return switch (gender) { + case FEMALE -> 'M'; + case MALE -> 'H'; + }; } @Deprecated @@ -94,13 +108,9 @@ public String generateInvalid(BaseProviders faker) { } /** - * Randomly gets a birthday. - * - * @param f A specific instance of Faker. - * @return A valid date. + * Formats given birthday to fit into ID Number */ - private String getBirthday(BaseProviders f) { - LocalDate birthday = f.timeAndDate().birthday(0, 120); + private String formatBirthday(LocalDate birthday) { return String.valueOf(birthday.getYear() * 10000 + birthday.getMonthValue() * 100 + birthday.getDayOfMonth()); } diff --git a/src/main/java/net/datafaker/idnumbers/MoldovanIdNumber.java b/src/main/java/net/datafaker/idnumbers/MoldovanIdNumber.java index be471c20c..92ca06d36 100644 --- a/src/main/java/net/datafaker/idnumbers/MoldovanIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/MoldovanIdNumber.java @@ -1,9 +1,14 @@ 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.LocalDate; +import static net.datafaker.idnumbers.Utils.birthday; +import static net.datafaker.idnumbers.Utils.randomGender; + /** * The Moldovan Individual Tax ID Number is 13 digits. *

@@ -14,7 +19,7 @@ */ public class MoldovanIdNumber implements IdNumberGenerator { - private static final int[] CHECKSUM_MASK = new int[]{7, 3, 1, 7, 3, 1, 7, 3, 1, 7, 3, 1}; + private static final int[] CHECKSUM_MASK = {7, 3, 1, 7, 3, 1, 7, 3, 1, 7, 3, 1}; @Override public String countryCode() { @@ -22,19 +27,20 @@ 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); + String basePart = basePart(faker, birthday); + String idNumber = basePart + checksum(basePart); + return new PersonIdNumber(idNumber, birthday, randomGender(faker)); } @Override public String generateInvalid(BaseProviders faker) { - String basePart = basePart(faker); + String basePart = basePart(faker, faker.timeAndDate().birthday()); return basePart + (checksum(basePart) + 1) % 10; } - private String basePart(BaseProviders faker) { - var birthday = faker.timeAndDate().birthday(0, 120); + private String basePart(BaseProviders faker, LocalDate birthday) { // IDNP: 2ГГГXXXYYYYYK return firstDigit() + ГГГ(birthday) + XXX(faker) + YYYYY(faker); } diff --git a/src/main/java/net/datafaker/idnumbers/PolishIdNumber.java b/src/main/java/net/datafaker/idnumbers/PolishIdNumber.java index f5e212bbd..5657f366c 100644 --- a/src/main/java/net/datafaker/idnumbers/PolishIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/PolishIdNumber.java @@ -1,9 +1,17 @@ package net.datafaker.idnumbers; import net.datafaker.providers.base.BaseProviders; +import net.datafaker.providers.base.IdNumber.GenderRequest; +import net.datafaker.providers.base.IdNumber.IdNumberRequest; +import net.datafaker.providers.base.PersonIdNumber; import java.time.LocalDate; -import java.util.Optional; + +import static net.datafaker.idnumbers.Utils.birthday; +import static net.datafaker.idnumbers.Utils.gender; +import static net.datafaker.idnumbers.Utils.randomGender; +import static net.datafaker.providers.base.PersonIdNumber.Gender.FEMALE; +import static net.datafaker.providers.base.PersonIdNumber.Gender.MALE; /** * Implementation based on the definition at @@ -19,30 +27,56 @@ public String countryCode() { return "PL"; } + /** + * @deprecated Use {@link GenderRequest} instead + */ + @Deprecated public enum Gender { MALE, FEMALE, ANY } @Override - public String generateValid(BaseProviders faker) { - return get(faker, faker.timeAndDate().birthday(0, 100), Gender.ANY); + public PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request) { + LocalDate birthday = birthday(faker, request); + PersonIdNumber.Gender gender = gender(faker, request); + return new PersonIdNumber(get(faker, birthday, gender), birthday, gender); + } + + /** + * @param requestedGender nullable + * @deprecated Use {@link #generateValid(BaseProviders, IdNumberRequest)} instead + */ + @Deprecated + public String get(BaseProviders faker, LocalDate birthDate, Gender requestedGender) { + PersonIdNumber.Gender gender = pickGender(faker, requestedGender); + return get(faker, birthDate, gender); + } + + private static PersonIdNumber.Gender pickGender(BaseProviders faker, Gender requestedGender) { + return requestedGender == null ? randomGender(faker) : + switch (requestedGender) { + case ANY -> randomGender(faker); + case MALE -> MALE; + case FEMALE -> FEMALE; + }; } - public String get(BaseProviders faker, LocalDate birthDate, Gender gender) { - int[] digits = generateDigits(faker, birthDate, Optional.ofNullable(gender).orElse(Gender.ANY)); + private String get(BaseProviders faker, LocalDate birthDate, PersonIdNumber.Gender gender) { + int[] digits = generateDigits(faker, birthDate, gender); int controlDigit = getControlDigit(digits); return toString(digits, controlDigit); } @Override public String generateInvalid(BaseProviders faker) { - int[] digits = generateDigits(faker, faker.timeAndDate().birthday(0, 100), Gender.ANY); + PersonIdNumber.Gender gender = randomGender(faker); + int[] digits = generateDigits(faker, faker.timeAndDate().birthday(), gender); int controlDigit = getControlDigit(digits); int invalidControlDigit = (controlDigit + 1) % 10; return toString(digits, invalidControlDigit); } - private int[] generateDigits(BaseProviders faker, LocalDate birthDate, Gender gender) { + private int[] generateDigits(BaseProviders faker, LocalDate birthDate, PersonIdNumber.Gender gender) { int monthEncoded = getMonthEncoded(birthDate.getYear(), birthDate.getMonthValue()); return new int[]{ birthDate.getYear() / 10 % 10, @@ -72,7 +106,7 @@ private static String toString(int[] digits, int controlDigit) { } private int randomDigit(BaseProviders faker) { - return faker.random().nextInt(10); + return faker.number().randomDigit(); } private int getControlDigit(int[] digits) { @@ -81,11 +115,10 @@ private int getControlDigit(int[] digits) { return (10 - sum % 10) % 10; } - private int getGenderDigit(BaseProviders faker, Gender gender) { + private int getGenderDigit(BaseProviders faker, PersonIdNumber.Gender gender) { return switch (gender) { case FEMALE -> faker.random().nextInt(5) * 2; case MALE -> faker.random().nextInt(5) * 2 + 1; - default -> randomDigit(faker); }; } diff --git a/src/main/java/net/datafaker/idnumbers/PortugueseIdNumber.java b/src/main/java/net/datafaker/idnumbers/PortugueseIdNumber.java index b89ae0f62..6e96333a2 100644 --- a/src/main/java/net/datafaker/idnumbers/PortugueseIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/PortugueseIdNumber.java @@ -1,6 +1,11 @@ package net.datafaker.idnumbers; import net.datafaker.providers.base.BaseProviders; +import net.datafaker.providers.base.IdNumber.IdNumberRequest; +import net.datafaker.providers.base.PersonIdNumber; + +import static net.datafaker.idnumbers.Utils.gender; +import static net.datafaker.idnumbers.Utils.birthday; /** * Portuguese VAT identification number (NIF) @@ -44,6 +49,11 @@ public String generateValid(final BaseProviders faker) { return digits + digitSum; } + @Override + public PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request) { + return new PersonIdNumber(generateValid(faker), birthday(faker, request), gender(faker, request)); + } + private int calculateDigitSum(String numbers) { int checkSum = 0; for (int i = 1; i <= numbers.length(); i++) { diff --git a/src/main/java/net/datafaker/idnumbers/SingaporeIdNumber.java b/src/main/java/net/datafaker/idnumbers/SingaporeIdNumber.java index ba7b948ea..cf3671456 100644 --- a/src/main/java/net/datafaker/idnumbers/SingaporeIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/SingaporeIdNumber.java @@ -1,14 +1,21 @@ 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 net.datafaker.service.RandomService; -import java.util.EnumMap; -import java.util.Map; +import java.time.LocalDate; + +import static net.datafaker.idnumbers.Utils.gender; +import static net.datafaker.idnumbers.Utils.randomGender; +import static net.datafaker.providers.base.IdNumber.GenderRequest.ANY; /** * Generate number of UIN/FIN for Singapore. * Algorithm is given from http://www.ngiam.net/NRIC/ + * See ... */ public class SingaporeIdNumber implements IdNumberGenerator { @Override @@ -18,40 +25,44 @@ public String countryCode() { public enum Type {SINGAPOREAN_TWENTIETH_CENTURY, FOREIGNER_TWENTIETH_CENTURY, SINGAPOREAN_TWENTY_FIRST_CENTURY, FOREIGNER_TWENTY_FIRST_CENTURY} - private record NricType(char firstLetter, String matchLetters, int[] code, int initialValue) { - private String format(int[] digits) { - int value = initialValue; - StringBuilder id = new StringBuilder(String.valueOf(firstLetter)); - for (int i = 0; i < digits.length; i++) { - value += digits[i] * code[i]; - id.append(digits[i]); - } - value %= 11; - id.append(matchLetters.charAt(value)); - return id.toString(); - } + private static String format(LocalDate issueDate, boolean citizen, int[] randomDigits) { + int checkDigitInitialValue = issueDate.getYear() < 2000 ? 0 : 4; + char firstLetter = citizen ? centuryPrefixCitizen(issueDate) : centuryPrefixForeigner(issueDate); + String matchLetters = citizen ? UIN_LETTERS : FIN_LETTERS; + int checkDigit = checkDigitInitialValue; + + StringBuilder id = new StringBuilder(11); + id.append(firstLetter); + for (int i = 0; i < randomDigits.length; i++) { + checkDigit += randomDigits[i] * CODE[i]; + id.append(randomDigits[i]); } + checkDigit %= 11; + id.append(matchLetters.charAt(checkDigit)); + return id.toString(); + } private static final int[] CODE = {0, 2, 7, 6, 5, 4, 3, 2}; private static final String FIN_LETTERS = "XWUTRQPNMLK"; private static final String UIN_LETTERS = "JZIHGFEDCBA"; - private static final Map INITIALIZER = new EnumMap<>(Type.class); - static { - INITIALIZER.put(Type.SINGAPOREAN_TWENTIETH_CENTURY, new NricType('S', UIN_LETTERS, CODE, 0)); - INITIALIZER.put(Type.FOREIGNER_TWENTIETH_CENTURY, new NricType('F', FIN_LETTERS, CODE, 0)); - INITIALIZER.put(Type.SINGAPOREAN_TWENTY_FIRST_CENTURY, new NricType('T', UIN_LETTERS, CODE, 4)); - INITIALIZER.put(Type.FOREIGNER_TWENTY_FIRST_CENTURY, new NricType('G', FIN_LETTERS, CODE, 4)); + @Override + public String generateValid(BaseProviders faker) { + return generateValid(faker, new IdNumberRequest(0, 100, ANY)).idNumber(); } @Override - public String generateValid(BaseProviders faker) { - return getValidFIN(faker, faker.options().option( - Type.SINGAPOREAN_TWENTIETH_CENTURY, - Type.FOREIGNER_TWENTIETH_CENTURY, - Type.SINGAPOREAN_TWENTY_FIRST_CENTURY, - Type.FOREIGNER_TWENTY_FIRST_CENTURY - )); + public PersonIdNumber generateValid(BaseProviders faker, IdNumberRequest request) { + LocalDate birthDate = Utils.birthday(faker, request); + boolean citizen = faker.bool().bool(); + Gender gender = gender(faker, request); + return generateValidIdNumber(faker, birthDate, citizen, gender); + } + + private static PersonIdNumber generateValidIdNumber(BaseProviders faker, LocalDate birthDate, boolean citizen, Gender gender) { + int[] number = randomDigits(faker); + String idNumber = format(birthDate, citizen, number); + return new PersonIdNumber(idNumber, birthDate, gender); } @Override @@ -60,11 +71,40 @@ public String generateInvalid(BaseProviders faker) { } public static String getValidFIN(BaseProviders f, Type type) { + LocalDate birthDate = randomBirthDate(f, type); + boolean citizen = switch (type) { + case SINGAPOREAN_TWENTIETH_CENTURY, SINGAPOREAN_TWENTY_FIRST_CENTURY -> true; + case FOREIGNER_TWENTIETH_CENTURY, FOREIGNER_TWENTY_FIRST_CENTURY -> false; + }; + return generateValidIdNumber(f, birthDate, citizen, randomGender(f)).idNumber(); + } + + static LocalDate randomBirthDate(BaseProviders faker, Type type) { + int now = LocalDate.now().getYear(); + return switch (type) { + case SINGAPOREAN_TWENTIETH_CENTURY, + FOREIGNER_TWENTIETH_CENTURY -> faker.timeAndDate().birthday(now - 1900, now - 1999); + case SINGAPOREAN_TWENTY_FIRST_CENTURY, + FOREIGNER_TWENTY_FIRST_CENTURY -> faker.timeAndDate().birthday(now - 2000, now - 2099); + }; + } + + private static int[] randomDigits(BaseProviders f) { final RandomService random = f.random(); final int[] number = new int[7]; for (int i = 0; i < number.length; i++) { number[i] = random.nextInt(0, 9); } - return INITIALIZER.get(type).format(number); + return number; + } + + static char centuryPrefixCitizen(LocalDate issueDate) { + int century = issueDate.getYear() / 100; + return (char) ('A' + century - 1); + } + + static char centuryPrefixForeigner(LocalDate issueDate) { + int century = issueDate.getYear() / 100; + return (char) ('A' + century - 14); } } diff --git a/src/main/java/net/datafaker/idnumbers/SouthAfricanIdNumber.java b/src/main/java/net/datafaker/idnumbers/SouthAfricanIdNumber.java index c67844f23..d5c7458a1 100644 --- a/src/main/java/net/datafaker/idnumbers/SouthAfricanIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/SouthAfricanIdNumber.java @@ -1,16 +1,24 @@ 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 java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; +import static net.datafaker.idnumbers.Utils.gender; +import static net.datafaker.idnumbers.Utils.birthday; + /** * Implementation based on the definition at * https://en.wikipedia.org/wiki/South_African_identity_card */ public class SouthAfricanIdNumber implements IdNumberGenerator { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd"); @Override public String countryCode() { @@ -32,11 +40,21 @@ public String getValidSsn(BaseProviders faker) { * @return a valid social security number on faker */ @Override - public String generateValid(BaseProviders f) { - String candidate = DATE_TIME_FORMATTER.format(f.timeAndDate().birthday(0, 100)) - + f.numerify("####") + public PersonIdNumber generateValid(BaseProviders f, IdNumberRequest request) { + LocalDate birthday = birthday(f, request); + Gender gender = gender(f, request); + String basePart = DATE_TIME_FORMATTER.format(birthday) + + sequentialNumber(f, gender) + f.options().option(CODE_PATTERN); - return candidate + calculateChecksum(candidate, 12); + return new PersonIdNumber(basePart + calculateChecksum(basePart, 12), birthday, gender); + } + + static String sequentialNumber(BaseProviders f, Gender gender) { + int number = switch (gender) { + case FEMALE -> f.number().numberBetween(0, 5000); + case MALE -> f.number().numberBetween(5000, 10_000); + }; + return "%04d".formatted(number); } @Deprecated diff --git a/src/main/java/net/datafaker/idnumbers/SouthKoreanIdNumber.java b/src/main/java/net/datafaker/idnumbers/SouthKoreanIdNumber.java index 2272155e6..94eb9062e 100644 --- a/src/main/java/net/datafaker/idnumbers/SouthKoreanIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/SouthKoreanIdNumber.java @@ -1,10 +1,16 @@ package net.datafaker.idnumbers; import net.datafaker.providers.base.BaseProviders; -import net.datafaker.service.RandomService; +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.birthday; +import static net.datafaker.idnumbers.Utils.gender; +import static net.datafaker.providers.base.PersonIdNumber.Gender.MALE; + /** * Generate number of Resident Registration Number for Republic of Korea. *

@@ -23,50 +29,49 @@ public String getValidRrn(BaseProviders f) { } @Override - public String generateValid(BaseProviders f) { + public PersonIdNumber generateValid(BaseProviders f, IdNumberRequest request) { StringBuilder patternBuilder = new StringBuilder(); - LocalDate now = LocalDate.now(); + LocalDate birthday = birthday(f, request); String iso = f.nation().isoCountry(); - String gender = f.gender().shortBinaryTypes(); + Gender gender = gender(f, request); // 1st to 6th digits indicate date of birth - patternBuilder.append(generateDay(f.random(), now.getYear() - 65, - now.getMonthValue(), now.getDayOfMonth(), now.getYear() - 18, - now.getMonthValue(), now.getDayOfMonth())); + patternBuilder.append(generateDay(birthday)); // Matches RRN Pattern ( ######-####### ) patternBuilder.append('-'); // 7th digit indicates birth century, gender, nationality - patternBuilder.append(get7thDigit(now.getYear(), gender, iso)); + patternBuilder.append(get7thDigit(birthday.getYear(), gender, iso)); // From Oct 2020, 8 to 13 digits are randomized // 8th to 13th digits are random digits patternBuilder.append("######"); - return f.numerify(patternBuilder.toString()); + String idNumber = f.numerify(patternBuilder.toString()); + return new PersonIdNumber(idNumber, birthday, gender); } - private int get7thDigit(int year, String shortBinaryGender, String isoCountry) { + private int get7thDigit(int year, Gender gender, String isoCountry) { // Local starts with 1, foreigner starts with 5 int locality = isoCountry.equalsIgnoreCase("kr") ? 1 : 5; if (year < 1900) { // Male: 9 | Female: 0 - return shortBinaryGender.equalsIgnoreCase("m") ? 9 : 0; + return gender == MALE ? 9 : 0; } else if (year < 2000) { // Male: 1, 5 | Female: 2, 6 - return locality + (shortBinaryGender.equalsIgnoreCase("m") ? 0 : 1); + return locality + (gender == MALE ? 0 : 1); } else { // Male: 3, 7 | Female: 4, 8 - return locality + (shortBinaryGender.equalsIgnoreCase("m") ? 2 : 3); + return locality + (gender == MALE ? 2 : 3); } } - private String generateDay(RandomService rand, int yearStart, int monthStart, int dayStart, int yearEnd, int monthEnd, int dayEnd) { - final int year = rand.nextInt(yearStart, yearEnd) % 100; - final int month = rand.nextInt(monthStart, monthEnd); - final int day = rand.nextInt(dayStart, dayEnd); + private String generateDay(LocalDate birthday) { + final int year = birthday.getYear() % 100; + final int month = birthday.getMonthValue(); + final int day = birthday.getDayOfMonth(); final char[] res = new char[6]; res[0] = (char) ('0' + (year / 10)); res[1] = (char) ('0' + (year % 10)); diff --git a/src/main/java/net/datafaker/idnumbers/SwedenIdNumber.java b/src/main/java/net/datafaker/idnumbers/SwedenIdNumber.java index 79eb996ea..2d28a0e08 100644 --- a/src/main/java/net/datafaker/idnumbers/SwedenIdNumber.java +++ b/src/main/java/net/datafaker/idnumbers/SwedenIdNumber.java @@ -1,11 +1,17 @@ package net.datafaker.idnumbers; import net.datafaker.providers.base.BaseProviders; +import net.datafaker.providers.base.IdNumber; +import net.datafaker.providers.base.PersonIdNumber; import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.time.temporal.ChronoField; +import static net.datafaker.idnumbers.Utils.gender; +import static net.datafaker.idnumbers.Utils.birthday; + /** * Implementation based on the definition at * https://www.skatteverket.se/privat/folkbokforing/personnummer.4.3810a01c150939e893f18c29.html @@ -13,6 +19,8 @@ * https://en.wikipedia.org/wiki/Personal_identity_number_(Sweden) */ public class SwedenIdNumber implements IdNumberGenerator { + private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd"); + @Override public String countryCode() { return "SE"; @@ -27,12 +35,14 @@ public String getValidSsn(BaseProviders f) { } @Override - public String generateValid(BaseProviders f) { + public PersonIdNumber generateValid(BaseProviders f, IdNumber.IdNumberRequest request) { + LocalDate birthday = birthday(f, request); String end = f.numerify("###"); - String candidate = DATE_TIME_FORMATTER.format(f.timeAndDate().birthday(0, 100)) + String basePart = DATE_TIME_FORMATTER.format(birthday) + f.options().option(PLUS_MINUS) + end; - return candidate + calculateChecksum(candidate); + String idNumber = basePart + calculateChecksum(basePart); + return new PersonIdNumber(idNumber, birthday, gender(f, request)); } @Deprecated diff --git a/src/main/java/net/datafaker/idnumbers/Utils.java b/src/main/java/net/datafaker/idnumbers/Utils.java new file mode 100644 index 000000000..32a9b17f1 --- /dev/null +++ b/src/main/java/net/datafaker/idnumbers/Utils.java @@ -0,0 +1,32 @@ +package net.datafaker.idnumbers; + +import net.datafaker.providers.base.BaseProviders; +import net.datafaker.providers.base.IdNumber; +import net.datafaker.providers.base.IdNumber.IdNumberRequest; +import net.datafaker.providers.base.PersonIdNumber.Gender; + +import java.time.LocalDate; + +import static net.datafaker.providers.base.PersonIdNumber.Gender.FEMALE; +import static net.datafaker.providers.base.PersonIdNumber.Gender.MALE; + +public class Utils { + + static LocalDate birthday(BaseProviders faker, IdNumberRequest request) { + return faker.timeAndDate().birthday(request.minAge(), request.maxAge()); + } + + static Gender gender(BaseProviders faker, IdNumberRequest request) { + IdNumber.GenderRequest gender = request.gender(); + return switch (gender) { + case FEMALE -> FEMALE; + case MALE -> MALE; + case ANY -> randomGender(faker); + }; + } + + static Gender randomGender(BaseProviders faker) { + return faker.bool().bool() ? FEMALE : MALE; + } + +} diff --git a/src/main/java/net/datafaker/providers/base/AbstractProvider.java b/src/main/java/net/datafaker/providers/base/AbstractProvider.java index 5663180b8..314c2fa52 100644 --- a/src/main/java/net/datafaker/providers/base/AbstractProvider.java +++ b/src/main/java/net/datafaker/providers/base/AbstractProvider.java @@ -37,7 +37,7 @@ public int hashCode() { return getClass().hashCode(); } - protected List loadGenerators(Class generatorClass) { + protected final List loadGenerators(Class generatorClass) { return ServiceLoader.load(generatorClass).stream() .map(ServiceLoader.Provider::get) .toList(); diff --git a/src/main/java/net/datafaker/providers/base/IdNumber.java b/src/main/java/net/datafaker/providers/base/IdNumber.java index fd2aca4c6..51c61cd6d 100644 --- a/src/main/java/net/datafaker/providers/base/IdNumber.java +++ b/src/main/java/net/datafaker/providers/base/IdNumber.java @@ -48,9 +48,28 @@ public String invalid() { .orElseGet(() -> faker.numerify(faker.resolve("id_number.invalid"))); } + public PersonIdNumber valid(IdNumberRequest request) { + return countryProvider() + .map(p -> p.generateValid(faker, request)) + .orElseThrow(() -> new IllegalArgumentException("ID Number generation not supported for country '%s'".formatted(country()))); + } + + public record IdNumberRequest( + int minAge, + int maxAge, + GenderRequest gender + ) {} + + public enum GenderRequest { + FEMALE, MALE, ANY + } + private Optional countryProvider() { - String country = faker.getContext().getLocale().getCountry(); - return Optional.ofNullable(countryProviders.get(country)); + return Optional.ofNullable(countryProviders.get(country())); + } + + private String country() { + return faker.getContext().getLocale().getCountry(); } public String ssnValid() { diff --git a/src/main/java/net/datafaker/providers/base/PersonIdNumber.java b/src/main/java/net/datafaker/providers/base/PersonIdNumber.java new file mode 100644 index 000000000..1a5d1a401 --- /dev/null +++ b/src/main/java/net/datafaker/providers/base/PersonIdNumber.java @@ -0,0 +1,13 @@ +package net.datafaker.providers.base; + +import java.time.LocalDate; + +public record PersonIdNumber( + String idNumber, + LocalDate birthDate, + Gender gender +) { + public enum Gender { + FEMALE, MALE + } +} diff --git a/src/main/java/net/datafaker/service/RandomService.java b/src/main/java/net/datafaker/service/RandomService.java index f2f7584a2..927ae125b 100644 --- a/src/main/java/net/datafaker/service/RandomService.java +++ b/src/main/java/net/datafaker/service/RandomService.java @@ -33,7 +33,7 @@ public int nextInt(int n) { } public Integer nextInt(int min, int max) { - return random.nextInt((max - min) + 1) + min; + return random.nextInt(min, max + 1); } @SuppressWarnings("unused") diff --git a/src/test/java/net/datafaker/idnumbers/AlbanianIdNumberTest.java b/src/test/java/net/datafaker/idnumbers/AlbanianIdNumberTest.java index 020b1513f..4af3d8a6b 100644 --- a/src/test/java/net/datafaker/idnumbers/AlbanianIdNumberTest.java +++ b/src/test/java/net/datafaker/idnumbers/AlbanianIdNumberTest.java @@ -5,6 +5,8 @@ import org.junit.jupiter.api.Test; import static java.lang.Integer.parseInt; +import static net.datafaker.providers.base.PersonIdNumber.Gender.FEMALE; +import static net.datafaker.providers.base.PersonIdNumber.Gender.MALE; import static org.assertj.core.api.Assertions.assertThat; class AlbanianIdNumberTest { @@ -44,14 +46,14 @@ void yy() { @Test void mm() { - assertThat(generator.mm(1, false)).isEqualTo("01"); - assertThat(generator.mm(2, false)).isEqualTo("02"); - assertThat(generator.mm(9, false)).isEqualTo("09"); - assertThat(generator.mm(12, false)).isEqualTo("12"); - assertThat(generator.mm(1, true)).isEqualTo("51"); - assertThat(generator.mm(2, true)).isEqualTo("52"); - assertThat(generator.mm(8, true)).isEqualTo("58"); - assertThat(generator.mm(12, true)).isEqualTo("62"); + assertThat(generator.mm(1, MALE)).isEqualTo("01"); + assertThat(generator.mm(2, MALE)).isEqualTo("02"); + assertThat(generator.mm(9, MALE)).isEqualTo("09"); + assertThat(generator.mm(12, MALE)).isEqualTo("12"); + assertThat(generator.mm(1, FEMALE)).isEqualTo("51"); + assertThat(generator.mm(2, FEMALE)).isEqualTo("52"); + assertThat(generator.mm(8, FEMALE)).isEqualTo("58"); + assertThat(generator.mm(12, FEMALE)).isEqualTo("62"); } @Test diff --git a/src/test/java/net/datafaker/idnumbers/EstonianIdNumberTest.java b/src/test/java/net/datafaker/idnumbers/EstonianIdNumberTest.java index 389e297b8..860ba1203 100644 --- a/src/test/java/net/datafaker/idnumbers/EstonianIdNumberTest.java +++ b/src/test/java/net/datafaker/idnumbers/EstonianIdNumberTest.java @@ -1,7 +1,12 @@ package net.datafaker.idnumbers; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import static net.datafaker.idnumbers.EstonianIdNumber.firstDigit; +import static net.datafaker.providers.base.PersonIdNumber.Gender.FEMALE; +import static net.datafaker.providers.base.PersonIdNumber.Gender.MALE; import static org.assertj.core.api.Assertions.assertThat; class EstonianIdNumberTest { @@ -16,4 +21,32 @@ void checksum() { assertThat(EstonianIdNumber.checksum("5110712176")).isEqualTo(0); assertThat(EstonianIdNumber.checksum("6110712176")).isEqualTo(0); } + + @ParameterizedTest + @ValueSource(ints = {1800, 1801, 1802, 1888, 1898, 1899}) + void firstDigit_18xx(int year) { + assertThat(firstDigit(year, MALE)).isEqualTo(1); + assertThat(firstDigit(year, FEMALE)).isEqualTo(2); + } + + @ParameterizedTest + @ValueSource(ints = {1900, 1901, 1902, 1988, 1998, 1999}) + void firstDigit_19xx(int year) { + assertThat(firstDigit(year, MALE)).isEqualTo(3); + assertThat(firstDigit(year, FEMALE)).isEqualTo(4); + } + + @ParameterizedTest + @ValueSource(ints = {2000, 2001, 2002, 2088, 2098, 2099}) + void firstDigit_20xx(int year) { + assertThat(firstDigit(year, MALE)).isEqualTo(5); + assertThat(firstDigit(year, FEMALE)).isEqualTo(6); + } + + @ParameterizedTest + @ValueSource(ints = {2100, 2101, 2102, 2188, 2198, 2199}) + void firstDigit_21xx(int year) { + assertThat(firstDigit(year, MALE)).isEqualTo(7); + assertThat(firstDigit(year, FEMALE)).isEqualTo(8); + } } diff --git a/src/test/java/net/datafaker/idnumbers/SingaporeIdNumberTest.java b/src/test/java/net/datafaker/idnumbers/SingaporeIdNumberTest.java new file mode 100644 index 000000000..e5e665fd6 --- /dev/null +++ b/src/test/java/net/datafaker/idnumbers/SingaporeIdNumberTest.java @@ -0,0 +1,53 @@ +package net.datafaker.idnumbers; + +import net.datafaker.Faker; +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static net.datafaker.idnumbers.SingaporeIdNumber.Type.FOREIGNER_TWENTIETH_CENTURY; +import static net.datafaker.idnumbers.SingaporeIdNumber.Type.FOREIGNER_TWENTY_FIRST_CENTURY; +import static net.datafaker.idnumbers.SingaporeIdNumber.Type.SINGAPOREAN_TWENTIETH_CENTURY; +import static net.datafaker.idnumbers.SingaporeIdNumber.Type.SINGAPOREAN_TWENTY_FIRST_CENTURY; +import static net.datafaker.idnumbers.SingaporeIdNumber.centuryPrefixCitizen; +import static net.datafaker.idnumbers.SingaporeIdNumber.centuryPrefixForeigner; +import static net.datafaker.idnumbers.SingaporeIdNumber.randomBirthDate; +import static org.assertj.core.api.Assertions.assertThat; + +class SingaporeIdNumberTest { + @Test + void centuryPrefix_forCitizens() { + assertThat(centuryPrefixCitizen(LocalDate.parse("1999-12-31"))).as("19xx = S").isEqualTo('S'); + assertThat(centuryPrefixCitizen(LocalDate.parse("2000-12-31"))).as("20xx = T").isEqualTo('T'); + assertThat(centuryPrefixCitizen(LocalDate.parse("2001-01-01"))).as("20xx = T").isEqualTo('T'); + assertThat(centuryPrefixCitizen(LocalDate.parse("2101-01-01"))).as("21xx = U").isEqualTo('U'); + assertThat(centuryPrefixCitizen(LocalDate.parse("2201-01-01"))).as("22xx = V").isEqualTo('V'); + } + + @Test + void centuryPrefix_forForeigner() { + assertThat(centuryPrefixForeigner(LocalDate.parse("1999-12-31"))).as("19xx = F").isEqualTo('F'); + assertThat(centuryPrefixForeigner(LocalDate.parse("2000-12-31"))).as("20xx = G").isEqualTo('G'); + assertThat(centuryPrefixForeigner(LocalDate.parse("2001-01-01"))).as("20xx = G").isEqualTo('G'); + assertThat(centuryPrefixForeigner(LocalDate.parse("2101-01-01"))).as("21xx = H").isEqualTo('H'); + assertThat(centuryPrefixForeigner(LocalDate.parse("2201-01-01"))).as("22xx = I").isEqualTo('I'); + } + + @Test + void randomBirthDate_20th_century() { + Faker faker = new Faker(); + for (int i = 0; i < 100; i++) { + assertThat(randomBirthDate(faker, SINGAPOREAN_TWENTIETH_CENTURY).getYear() / 100).isEqualTo(19); + assertThat(randomBirthDate(faker, FOREIGNER_TWENTIETH_CENTURY).getYear() / 100).isEqualTo(19); + } + } + + @Test + void randomBirthDate_21th_century() { + Faker faker = new Faker(); + for (int i = 0; i < 100; i++) { + assertThat(randomBirthDate(faker, SINGAPOREAN_TWENTY_FIRST_CENTURY).getYear() / 100).isEqualTo(20); + assertThat(randomBirthDate(faker, FOREIGNER_TWENTY_FIRST_CENTURY).getYear() / 100).isEqualTo(20); + } + } +} diff --git a/src/test/java/net/datafaker/idnumbers/SouthAfricanIdNumberTest.java b/src/test/java/net/datafaker/idnumbers/SouthAfricanIdNumberTest.java index 6ddcbad8f..5c9c567c3 100644 --- a/src/test/java/net/datafaker/idnumbers/SouthAfricanIdNumberTest.java +++ b/src/test/java/net/datafaker/idnumbers/SouthAfricanIdNumberTest.java @@ -7,7 +7,11 @@ import java.util.Locale; +import static java.lang.Integer.parseInt; import static net.datafaker.idnumbers.SouthAfricanIdNumber.isValidEnZASsn; +import static net.datafaker.idnumbers.SouthAfricanIdNumber.sequentialNumber; +import static net.datafaker.providers.base.PersonIdNumber.Gender.FEMALE; +import static net.datafaker.providers.base.PersonIdNumber.Gender.MALE; import static org.assertj.core.api.Assertions.assertThat; /* @@ -39,4 +43,22 @@ void testSsnFormat() { assertThat(f.idNumber().valid()).matches("\\d{10}[01]8\\d"); assertThat(f.idNumber().invalid()).matches("\\d{10}[01]8\\d"); } + + @RepeatedTest(10000) + void sequentialNumber_forMales() { + BaseFaker f = new BaseFaker(new Locale("en", "ZA")); + String sequentialNumber = sequentialNumber(f, MALE); + + assertThat(sequentialNumber).matches("\\d{4}"); + assertThat(parseInt(sequentialNumber)).isGreaterThanOrEqualTo(5000); + } + + @RepeatedTest(10000) + void sequentialNumber_forFemales() { + BaseFaker f = new BaseFaker(new Locale("en", "ZA")); + String sequentialNumber = sequentialNumber(f, FEMALE); + + assertThat(sequentialNumber).matches("\\d{4}"); + assertThat(parseInt(sequentialNumber)).isLessThan(5000); + } } diff --git a/src/test/java/net/datafaker/service/RandomServiceTest.java b/src/test/java/net/datafaker/service/RandomServiceTest.java index 22a1b6f8a..11b5994ec 100644 --- a/src/test/java/net/datafaker/service/RandomServiceTest.java +++ b/src/test/java/net/datafaker/service/RandomServiceTest.java @@ -53,6 +53,16 @@ void testIntInRange(RandomService randomService) { } } + @Test + void nextInt_returnsValueWithinGivenRange() { + RandomService randomService = new RandomService(); + for (int i = 0; i < 10_000; i++) { + assertThat(randomService.nextInt(2, 6)) + .isGreaterThanOrEqualTo(2) + .isLessThanOrEqualTo(6); + } + } + @Test void predictableRandomRange() { RandomService randomService = new RandomService(new Random(10));