diff --git a/src/main/java/net/datafaker/providers/base/Locality.java b/src/main/java/net/datafaker/providers/base/Locality.java index 225717930..9c9c4dc81 100644 --- a/src/main/java/net/datafaker/providers/base/Locality.java +++ b/src/main/java/net/datafaker/providers/base/Locality.java @@ -29,7 +29,8 @@ public class Locality extends AbstractProvider { private final List locales; - private List shuffledLocales = new ArrayList<>(); + private final List shuffledLocales = new ArrayList<>(); + private int shuffledLocaleIndex = 0; /** * Constructor for Locality class @@ -162,18 +163,17 @@ public String localeStringWithoutReplacement() { * @param random random number generator (can utilize seed for deterministic random selection) * @return String of a randomly selected locale (eg. "es", "es-MX") */ - public String localeStringWithoutReplacement(Random random) { - if (this.shuffledLocales.isEmpty()) { + public synchronized String localeStringWithoutReplacement(Random random) { + if (shuffledLocales.isEmpty() || shuffledLocaleIndex >= shuffledLocales.size() - 1) { // copy list of locales supported into shuffledLocales - shuffledLocales = new ArrayList<>(this.locales); + shuffledLocales.clear(); + shuffledLocales.addAll(this.locales); + shuffledLocaleIndex = 0; Collections.shuffle(shuffledLocales, random); } - // retrieve next locale in shuffledLocales and remove from list - String pickedLocale = shuffledLocales.get(shuffledLocales.size() - 1); - shuffledLocales.remove(shuffledLocales.size() - 1); - - return pickedLocale; + // retrieve next locale in shuffledLocales and increase the index + return shuffledLocales.get(shuffledLocaleIndex++); } } diff --git a/src/main/java/net/datafaker/providers/base/Time.java b/src/main/java/net/datafaker/providers/base/Time.java index 9225a6c05..0ec8939b5 100644 --- a/src/main/java/net/datafaker/providers/base/Time.java +++ b/src/main/java/net/datafaker/providers/base/Time.java @@ -134,7 +134,7 @@ public String past(int atMost, int minimum, ChronoUnit unit, String pattern) { */ public long between(LocalTime from, LocalTime to) throws IllegalArgumentException { if (to.isBefore(from)) { - throw new IllegalArgumentException("Invalid time range, the upper bound time is before the lower bound."); + throw new IllegalArgumentException("Invalid time range: the upper bound time (%s) is before the lower bound (%s)".formatted(to, from)); } if (from.equals(to)) { diff --git a/src/test/java/net/datafaker/providers/base/TimeTest.java b/src/test/java/net/datafaker/providers/base/TimeTest.java index a5a3a1a7e..4b9499fa4 100644 --- a/src/test/java/net/datafaker/providers/base/TimeTest.java +++ b/src/test/java/net/datafaker/providers/base/TimeTest.java @@ -1,16 +1,23 @@ package net.datafaker.providers.base; +import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; import java.time.LocalTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; +import java.util.regex.Pattern; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; public class TimeTest extends BaseFakerTest { + private static final Pattern RE_TIME_BETWEEN = Pattern.compile("[0-2][0-9]:[0-5][0-9]:[0-5][0-9]"); + private static final long NANOSECONDS_IN_DAY = 24L * 60 * 60 * 1000 * 1000_000L; + private static final long NANOSECONDS_IN_MINUTE = 60 * 1000 * 1000_000L; + @Test void testFutureTime() { LocalTime now = LocalTime.now(); @@ -80,16 +87,20 @@ void testBetweenThenLargerThanNow() { LocalTime then = now.plusSeconds(1); assertThatThrownBy(() -> faker.time().between(then, now)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Invalid time range, the upper bound time is before the lower bound."); + .hasMessage("Invalid time range: the upper bound time (%s) is before the lower bound (%s)".formatted(now, then)); } - @Test + @RepeatedTest(10) void testBetweenWithMask() { - String pattern = "mm:hh:ss"; - LocalTime now = LocalTime.now(); + String pattern = "HH:mm:ss"; + LocalTime now = LocalTime.ofNanoOfDay((long) (Math.random() * (NANOSECONDS_IN_DAY - NANOSECONDS_IN_MINUTE - 1))); LocalTime then = now.plusMinutes(1); - DateTimeFormatter.ofPattern(pattern).parse(faker.time().between(now, then, pattern)); + String result = faker.time().between(now, then, pattern); + assertThat(result).matches(RE_TIME_BETWEEN); + TemporalAccessor timeBetween = DateTimeFormatter.ofPattern(pattern).parse(result); + assertThat(timeBetween.query(LocalTime::from)).isAfter(now.minusSeconds(1)); + assertThat(timeBetween.query(LocalTime::from)).isBefore(then.plusSeconds(1)); } @Test