Gerando datas aleatórias em Java

Gerando datas aleatórias em Java

1. Visão geral

Neste tutorial, veremos como gerar datas e horas aleatórias de modos limitados e ilimitados.

Veremos como gerar esses valores usando a APIjava.util.Date legada e tambémnew date-time library do Java 8.

2. Data e hora aleatórias

Dates and times are nothing more than 32-bit integers compared to an epoch time, para que possamos gerar valores temporais aleatórios seguindo este algoritmo simples:

  1. Gere um número aleatório de 32 bits, umint

  2. Passe o valor aleatório gerado para um construtor ou construtor de data e hora apropriado

2.1. LimiteInstant

java.time.Instant is one of the new date and time additions in Java 8. Eles representam pontos instantâneos na linha do tempo.

Para gerar umInstant aleatório entre dois outros, podemos:

  1. Gere um número aleatório entre os segundos de época de determinadoInstants

  2. Crie oInstant  aleatório passando esse número aleatório para o métodoofEpochSecond() 

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);
}

A fim de obter mais rendimento em ambientes multi-threaded, estamos usandoThreadLocalRandom para gerar nossos números aleatórios.

Podemos verificar que oInstant gerado é sempre maior ou igual ao primeiroInstant and  é menor que o segundoInstant:

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);

Lembre-se, é claro, que testar a aleatoriedade é inerentemente não determinístico e geralmente não é recomendado em um aplicativo real.

Da mesma forma, também é possível gerar umInstant aleatório mais seguro ou antes de outro:

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

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

2.2. LimiteDate

One of the java.util.Date constructors take the number of milliseconds after the epoch. Então, podemos usar o mesmo algoritmo para gerar umDate aleatório entre dois outros:

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);
}

Da mesma forma, devemos poder verificar esse 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. Instant ilimitado

Para gerar umInstant totalmente aleatório, podemos simplesmente gerar um inteiro aleatório e passá-lo para o métodoofEpochSecond() :

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

Using 32-bit seconds since the epoch time generates more reasonable random times,, portanto, estamos usando o métodonextInt() aqui.

Além disso, esse valor ainda deve estar entre os valores deInstant  mínimos e máximos possíveis que o Java pode manipular:

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

2.4. Date ilimitado

Semelhante ao exemplo limitado, podemos passar um valor aleatório para o construtorDate’s para gerar umDate: aleatório

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

Como a unidade de tempo do construtor é de milissegundos, estamos convertendo os segundos de época de 32 bits em milissegundos, multiplicando-o por 1000.

Certamente, este valor ainda está entre os valores deDate  mínimos e máximos possíveis:

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 aleatória

Até agora, geramos temporais aleatórios contendo componentes de data e hora. Da mesma forma,we can use the concept of epoch days to generate random temporals with just date components.

Um dia da época é igual ao número de dias desde 1 de janeiro de 1970. Então, para gerar uma data aleatória,we just have to generate a random number and use that number as the epoch day.

3.1. Limite

Precisamos de uma abstração temporal contendo apenas componentes de data, entãojava.time.LocalDate parece um bom 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);
}

Aqui, estamos usando o métodotoEpochDay() para converter cadaLocalDate para seu dia de época correspondente. Da mesma forma, podemos verificar se esta abordagem está correta:

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. Não consolidado

Para gerar datas aleatórias, independentemente de qualquer intervalo, podemos simplesmente gerar um dia de época aleatório:

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

Nosso gerador de datas aleatórias escolhe um dia aleatório entre 100 anos antes e depois da época. Novamente, a lógica por trás disso é gerar valores de data razoáveis:

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

4. Tempo aleatório

Semelhante ao que fizemos com datas, podemos gerar temporais aleatórios com apenas componentes de tempo. Para fazer isso,we can use the second of the day concept. Ou seja,a random time is equal to a random number representing the seconds since the beginning of the day.

4.1. Limite

Ojava.time.LocalTime class é uma abstração temporal que encapsula nada além de componentes de tempo:

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);
}

Para gerar um tempo aleatório entre dois outros, podemos:

  1. Gere um número aleatório entre o segundo dia do horário especificado

  2. Crie um horário aleatório usando esse número aleatório

Podemos facilmente verificar o comportamento desse algoritmo aleatório de geração de tempo:

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. Não consolidado

Até valores de tempo ilimitados devem estar no intervalo de 00:00:00 até 23:59:59, para que possamos simplesmente implementar essa lógica por delegação:

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

5. Conclusão

Neste tutorial, reduzimos a definição de datas e horários aleatórios para números aleatórios. Em seguida, vimos como essa redução nos ajudou a gerar valores temporais aleatórios comportando-se como registros de data e hora, datas ou horas.

Como de costume, o código de amostra está disponível emGitHub.