Перейти к основному содержимому

Генерация случайных дат в Java

· 5 мин. чтения

1. Обзор

В этом уроке мы увидим, как генерировать случайные даты и время ограниченным и неограниченным образом.

Мы рассмотрим, как генерировать эти значения, используя устаревший API java.util.Date , а также новую библиотеку даты и времени из Java 8.

2. Случайная дата и время

Даты и время представляют собой не что иное, как 32-битные целые числа по сравнению с временем эпохи , поэтому мы можем генерировать случайные временные значения, следуя этому простому алгоритму:

  1. Генерировать случайное 32-битное число, int
  2. Передайте сгенерированное случайное значение соответствующему конструктору или построителю даты и времени.

2.1. Ограниченный Мгновенный

java.time.Instant — одно из новых дополнений даты и времени в Java 8. `` Они представляют мгновенные точки на временной шкале .

Чтобы сгенерировать случайный Instant между двумя другими, мы можем:

  1. Генерировать случайное число между секундами эпохи заданных моментов
  2. Создайте случайный Instant , передав это случайное число методу 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);
}

Чтобы добиться большей пропускной способности в многопоточных средах, мы используем ThreadLocalRandom для генерации наших случайных чисел.

Мы можем убедиться, что сгенерированный Instant всегда больше или равен первому Instant и меньше второго 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);

Помните, конечно, что тестирование случайности по своей сути недетерминировано и обычно не рекомендуется в реальном приложении.

Точно так же можно сгенерировать случайный Instant после или перед другим:

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

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

2.2. Ограниченная дата

Один из конструкторов java.util.Date принимает количество миллисекунд после эпохи. Итак, мы можем использовать тот же алгоритм для генерации случайной даты между двумя другими:

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

Точно так же мы должны быть в состоянии проверить это поведение:

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 , мы можем просто сгенерировать случайное целое число и передать его методу ofEpochSecond() :

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

Использование 32-битных секунд с начала эпохи генерирует более разумные случайные моменты времени, поэтому здесь мы используем метод nextInt () .

Кроме того, это значение должно быть между минимальным и максимальным возможными значениями Instant , которые может обрабатывать Java:

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

2.4. Неограниченная дата

Подобно ограниченному примеру, мы можем передать случайное значение конструктору Date, чтобы сгенерировать случайную дату:

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

Поскольку `` единицей времени конструктора являются миллисекунды, мы конвертируем 32-битные секунды эпохи в миллисекунды, умножая их на 1000.

Конечно, это значение все еще находится между минимально и максимально возможными значениями даты :

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. Случайная дата

До сих пор мы генерировали случайные временные объекты, содержащие компоненты даты и времени. Точно так же мы можем использовать концепцию дней эпохи для генерации случайных временных значений только с компонентами даты.

День эпохи равен количеству дней, прошедших с 1 января 1970 года. Поэтому, чтобы сгенерировать случайную дату, нам просто нужно сгенерировать случайное число и использовать это число в качестве дня эпохи.

3.1. Ограниченный

Нам нужна временная абстракция, содержащая только компоненты даты, поэтому java.time.LocalDate кажется хорошим кандидатом:

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

Здесь мы используем метод toEpochDay() для преобразования каждой LocalDate в соответствующий день эпохи. Точно так же мы можем проверить правильность этого подхода:

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

3.2. Неограниченный

Чтобы сгенерировать случайные даты независимо от диапазона, мы можем просто сгенерировать случайный день эпохи:

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

Наш генератор случайных дат выбирает случайный день из 100 лет до и после эпохи. Опять же, причина этого заключается в создании разумных значений даты:

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

4. Случайное время

Подобно тому, что мы делали с датами, мы можем генерировать случайные временные значения только с компонентами времени. Для этого мы можем использовать концепцию второго дня. То есть случайное время равно случайному числу, представляющему секунды с начала дня.

4.1. Ограниченный

Класс java.time.LocalTime представляет собой временную абстракцию, которая инкапсулирует только компоненты времени:

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

Чтобы сгенерировать случайное время между двумя другими, мы можем:

  1. Генерировать случайное число между секундой дня заданного времени
  2. Создайте случайное время, используя это случайное число

Мы можем легко проверить поведение этого алгоритма генерации случайного времени:

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. Неограниченный

Даже неограниченные значения времени должны находиться в диапазоне от 00:00:00 до 23:59:59, поэтому мы можем просто реализовать эту логику путем делегирования:

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

5. Вывод

В этом уроке мы сократили определение случайных дат и времени до случайных чисел. Затем мы увидели, как это сокращение помогло нам генерировать случайные временные значения, ведущие себя как метки времени, даты или время.

Как обычно, пример кода доступен на GitHub .