Generazione di date casuali in Java

1. Panoramica

In questo tutorial, vedremo come generare date e orari casuali in mode limitate e illimitate.

Vedremo come generare questi valori utilizzando l' API java.util.Date legacy e anche la nuova libreria data-ora da Java 8.

2. Data e ora casuali

Le date e le ore non sono altro che interi a 32 bit rispetto a un tempo di un'epoca , quindi possiamo generare valori temporali casuali seguendo questo semplice algoritmo:

  1. Genera un numero casuale a 32 bit, un int
  2. Passare il valore casuale generato a un costruttore o builder di data e ora appropriato

2.1. Istantaneo limitato

java.time.I nstant è una delle nuove aggiunte di data e ora in Java 8. Rappresentano punti istantanei sulla linea temporale.

Per generare un istantaneo casuale tra altri due, possiamo:

  1. Genera un numero casuale compreso tra i secondi epoca dei dati Instants
  2. Crea l' istante casuale passando quel numero casuale al metodo ofEpochSecond ()
public static Instant between(Instant startInclusive, Instant endExclusive) { long startSeconds = startInclusive.getEpochSecond(); long endSeconds = endExclusive.getEpochSecond(); long random = ThreadLocalRandom .current() .nextLong(startSeconds, endSeconds); return Instant.ofEpochSecond(random); }

Per ottenere più throughput in ambienti multi-thread, stiamo usando ThreadLocalRandom per generare i nostri numeri casuali.

Possiamo verificare che l' istante generato è sempre maggiore o uguale al primo istante ed è minore del secondo istante:

Instant hundredYearsAgo = Instant.now().minus(Duration.ofDays(100 * 365)); Instant tenDaysAgo = Instant.now().minus(Duration.ofDays(10)); Instant random = RandomDateTimes.between(hundredYearsAgo, tenDaysAgo); assertThat(random).isBetween(hundredYearsAgo, tenDaysAgo);

Ricorda, ovviamente, che il test della casualità è intrinsecamente non deterministico e generalmente non è raccomandato in un'applicazione reale.

Allo stesso modo, è anche possibile generare un istantaneo casuale dopo o prima di un altro:

public static Instant after(Instant startInclusive) { return between(startInclusive, Instant.MAX); } public static Instant before(Instant upperExclusive) { return between(Instant.MIN, upperExclusive); }

2.2. Data limitata

Uno dei costruttori java.util.Date prende il numero di millisecondi dopo l'epoca. Quindi, possiamo usare lo stesso algoritmo per generare una data casuale tra altre due:

public static Date between(Date startInclusive, Date endExclusive) { long startMillis = startInclusive.getTime(); long endMillis = endExclusive.getTime(); long randomMillisSinceEpoch = ThreadLocalRandom .current() .nextLong(startMillis, endMillis); return new Date(randomMillisSinceEpoch); }

Allo stesso modo, dovremmo essere in grado di verificare questo comportamento:

long aDay = TimeUnit.DAYS.toMillis(1); long now = new Date().getTime(); Date hundredYearsAgo = new Date(now - aDay * 365 * 100); Date tenDaysAgo = new Date(now - aDay * 10); Date random = LegacyRandomDateTimes.between(hundredYearsAgo, tenDaysAgo); assertThat(random).isBetween(hundredYearsAgo, tenDaysAgo);

2.3. Istantaneo illimitato

Per generare un Instant totalmente casuale , possiamo semplicemente generare un numero intero casuale e passarlo al metodo ofEpochSecond () :

public static Instant timestamp() { return Instant.ofEpochSecond(ThreadLocalRandom.current().nextInt()); }

L'uso di secondi a 32 bit dall'epoca dell'epoca genera tempi casuali più ragionevoli, quindi qui stiamo usando il metodo nextInt () .

Inoltre, questo valore dovrebbe essere ancora compreso tra i valori Instant minimi e massimi possibili che Java può gestire:

Instant random = RandomDateTimes.timestamp(); assertThat(random).isBetween(Instant.MIN, Instant.MAX);

2.4. Data illimitata

Simile all'esempio delimitato, possiamo passare un valore casuale al costruttore di Date per generare una data casuale :

public static Date timestamp() { return new Date(ThreadLocalRandom.current().nextInt() * 1000L); }

Dal momento che ill'unità di tempo del costruttore è millisecondi, stiamo convertendo i secondi dell'epoca a 32 bit in millisecondi moltiplicandoli per 1000.

Certamente, questo valore è ancora compreso tra i valori di Data minimo e massimo possibili :

Date MIN_DATE = new Date(Long.MIN_VALUE); Date MAX_DATE = new Date(Long.MAX_VALUE); Date random = LegacyRandomDateTimes.timestamp(); assertThat(random).isBetween(MIN_DATE, MAX_DATE);

3. Data casuale

Fino ad ora, abbiamo generato temporali casuali contenenti componenti di data e ora. Allo stesso modo, possiamo usare il concetto di epoche per generare temporali casuali con solo componenti di data.

Un giorno epocale è uguale al numero di giorni dal 1 gennaio 1970. Quindi, per generare una data casuale, dobbiamo solo generare un numero casuale e utilizzare quel numero come giorno epocale.

3.1. Delimitato

Abbiamo bisogno di un'astrazione temporale contenente solo componenti di data, quindi java.time.LocalDate sembra un buon candidato:

public static LocalDate between(LocalDate startInclusive, LocalDate endExclusive) { long startEpochDay = startInclusive.toEpochDay(); long endEpochDay = endExclusive.toEpochDay(); long randomDay = ThreadLocalRandom .current() .nextLong(startEpochDay, endEpochDay); return LocalDate.ofEpochDay(randomDay); }

Qui stiamo usando il metodo toEpochDay () per convertire ogni LocalDate nel corrispondente giorno dell'epoca. Allo stesso modo, possiamo verificare che questo approccio sia corretto:

LocalDate start = LocalDate.of(1989, Month.OCTOBER, 14); LocalDate end = LocalDate.now(); LocalDate random = RandomDates.between(start, end); assertThat(random).isBetween(start, end);

3.2. Illimitato

Per generare date casuali indipendentemente da qualsiasi intervallo, possiamo semplicemente generare un giorno dell'epoca casuale:

public static LocalDate date() { int hundredYears = 100 * 365; return LocalDate.ofEpochDay(ThreadLocalRandom .current().nextInt(-hundredYears, hundredYears)); }

Il nostro generatore di date casuali sceglie un giorno casuale tra 100 anni prima e dopo l'epoca. Ancora una volta, la logica alla base di questo è generare valori di data ragionevoli:

LocalDate randomDay = RandomDates.date(); assertThat(randomDay).isBetween(LocalDate.MIN, LocalDate.MAX);

4. Tempo casuale

Simile a quello che abbiamo fatto con le date, possiamo generare temporali casuali con solo componenti temporali. Per fare ciò, possiamo usare il concetto del secondo della giornata. Cioè, un tempo casuale è uguale a un numero casuale che rappresenta i secondi dall'inizio della giornata.

4.1. Delimitato

La classe java.time.LocalTime è un'astrazione temporale che incapsula nient'altro che componenti temporali:

public static LocalTime between(LocalTime startTime, LocalTime endTime) { int startSeconds = startTime.toSecondOfDay(); int endSeconds = endTime.toSecondOfDay(); int randomTime = ThreadLocalRandom .current() .nextInt(startSeconds, endSeconds); return LocalTime.ofSecondOfDay(randomTime); }

Per generare un tempo casuale tra due altri, possiamo:

  1. Genera un numero casuale tra il secondo del giorno degli orari indicati
  2. Crea un tempo casuale usando quel numero casuale

Possiamo facilmente verificare il comportamento di questo algoritmo di generazione del tempo casuale:

LocalTime morning = LocalTime.of(8, 30); LocalTime randomTime = RandomTimes.between(LocalTime.MIDNIGHT, morning); assertThat(randomTime) .isBetween(LocalTime.MIDNIGHT, morning) .isBetween(LocalTime.MIN, LocalTime.MAX);

4.2. Illimitato

Anche i valori di tempo illimitato dovrebbero essere compresi tra 00:00:00 e 23:59:59, quindi possiamo semplicemente implementare questa logica per delega:

public static LocalTime time() { return between(LocalTime.MIN, LocalTime.MAX); }

5. conclusione

In questo tutorial, abbiamo ridotto la definizione di date e ore casuali a numeri casuali. Quindi, abbiamo visto come questa riduzione ci ha aiutato a generare valori temporali casuali che si comportano come timestamp, date o ore.

Come al solito, il codice di esempio è disponibile su GitHub.