Java Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we're going to see how to generate random dates and times in bounded and unbounded fashions.

We'll be looking at how to generate these values using the legacy java.util.Date API and also the new date-time library from Java 8.

2. Random Date and Time

Dates and times are nothing more than 32-bit integers compared to an epoch time, so we can generate random temporal values by following this simple algorithm:

  1. Generate a random 32-bit number, an int
  2. Pass the generated random value to an appropriate date and time constructor or builder

2.1. Bounded Instant

java.time.Instant is one of the new date and time additions in Java 8. They represent instantaneous points on the time-line.

In order to generate a random Instant between two other ones, we can:

  1. Generate a random number between the epoch seconds of the given Instants
  2. Create the random Instant by passing that random number to the ofEpochSecond() method
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);
}

In order to achieve more throughput in multi-threaded environments, we're using the ThreadLocalRandom to generate our random numbers.

We can verify that the generated Instant is always greater than or equal to the first Instant and is less than the second Instant:

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

Remember, of course, that testing randomness is inherently non-deterministic and is generally not recommended in a real application.

Similarly, it's also possible to generate a random Instant after or before another one:

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

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

2.2. Bounded Date

One of the java.util.Date constructors take the number of milliseconds after the epoch. So, we can use the same algorithm to generate a random Date between two others:

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

Similarly, we should be able to verify this behavior:

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. Unbounded Instant

In order to generate a totally random Instant, we can simply generate a random integer and pass it to the ofEpochSecond() method:

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

Using 32-bit seconds since the epoch time generates more reasonable random times, hence we're using the nextInt() method here.

Also, this value should be still between the minimum and maximum possible Instant values that Java can handle:

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

2.4. Unbounded Date

Similar to the bounded example, we can pass a random value to Date's constructor to generate a random Date:

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

Since the constructor's time unit is milliseconds, we're converting the 32-bit epoch seconds to milliseconds by multiplying it by 1000.

Certainly, this value is still between the minimum and maximum possible Date values:

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. Random Date

Up until now, we generated random temporals containing both date and time components. Similarly, we can use the concept of epoch days to generate random temporals with just date components.

An epoch day is equal to the number of days since the 1 January 1970. So in order to generate a random date, we just have to generate a random number and use that number as the epoch day.

3.1. Bounded

We need a temporal abstraction containing only date components, so java.time.LocalDate seems a good candidate:

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

Here we're using the toEpochDay() method to convert each LocalDate to its corresponding epoch day. Similarly, we can verify that this approach is correct:

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. Unbounded

In order to generate random dates regardless of any range, we can simply generate a random epoch day:

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

Our random date generator chooses a random day from 100 years before and after the epoch. Again, the rationale behind this is to generate reasonable date values:

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

4. Random Time

Similar to what we did with dates, we can generate random temporals with just time components. In order to do that, we can use the second of the day concept. That is, a random time is equal to a random number representing the seconds since the beginning of the day.

4.1. Bounded

The java.time.LocalTime class is a temporal abstraction that encapsulates nothing but time components:

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

In order to generate a random time between two others, we can:

  1. Generate a random number between the second of the day of the given times
  2. Create a random time using that random number

We can easily verify the behavior of this random time generation algorithm:

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. Unbounded

Even unbounded time values should be in 00:00:00 until 23:59:59 range, so we can simply implement this logic by delegation:

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

5. Conclusion

In this tutorial, we reduced the definition of random dates and times to random numbers. Then, we saw how this reduction helped us to generate random temporal values behaving like timestamps, dates or times.

As usual, the sample code is available over on GitHub.

Java bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

Leave a Reply

avatar
  Subscribe  
Notify of