How to generate a random time between two times say 4PM and 2AM?
Asked Answered
B

2

7

I have tried using -

int startSeconds = restaurant.openingTime.toSecondOfDay();
int endSeconds = restaurant.closingTime.toSecondOfDay();
LocalTime timeBetweenOpenClose = LocalTime.ofSecondOfDay(ThreadLocalRandom.current().nextInt(startSeconds, endSeconds));

But this usually runs into an error as in nextInt(origin, bounds), origin can't be less than bounds which will happen if my openingTime is 16:00:00 and closingTime is 02:00:00.

Burmeister answered 3/4, 2021 at 10:43 Comment(2)
Am I correct in assuming that restaurant.openingTime and restaurant.closingTime are java.time.LocalTime too?Edwardedwardian
Yes, that's correct. Also, 4 PM belongs to today and 2 AM is post-midnight i.e. next day. @OleV.V.Burmeister
C
5

You can add the seconds of one day(24*60*60) when startSeconds is greater than endSeconds to represent the next day's second and after getting a random number modulo it by the seconds of one day to convert it into LocalTime by a valid second value.

int secondsInDay = (int)Duration.ofDays(1).getSeconds();
if(startSeconds > endSeconds){
  endSeconds += secondsInDay;
}
LocalTime timeBetweenOpenClose = LocalTime.ofSecondOfDay(
              ThreadLocalRandom.current().nextInt(startSeconds, endSeconds) % secondsInDay);
Charteris answered 3/4, 2021 at 11:16 Comment(0)
G
5

We cannot know how much time will elapse between 4 PM and 2 AM without applying a date and time zone. Therefore, we will solve it using ZonedDateTime.

  1. The first step will be: obtain a ZonedDateTime by calling LocalDate#atStartOfDay
ZoneId zoneId = ZoneId.systemDefault();
LocalDate.now().atStartOfDay(zoneId);
  1. Next, use ZonedDateTime#with to get a ZonedDateTime with the specified time.
  2. Now, you can derive an Instant from a ZonedDateTime using ZonedDateTime#toInstant.
  3. Once you have the start and end Instants derived this way, you can use ThreadLocalRandom.current().nextLong to generate a long value in the range of the start and the end Instants and use the obtained value to get the required Instant.
  4. Finally, you can derive a ZonedDateTime from this Instant using Instant#atZone and then get the required time using ZonedDateTime#toLocalTime.

Demo:

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.concurrent.ThreadLocalRandom;

public class Main {
    public static void main(String[] args) {
        // Change it as per the applicable timezone e.g. ZoneId.of("Europe/London")
        ZoneId zoneId = ZoneId.systemDefault();
        LocalDate today = LocalDate.now();
        
        ZonedDateTime zdtStart = today.atStartOfDay(zoneId)
                                      .with(LocalTime.of(16, 0));
        
        ZonedDateTime zdtEnd = today.plusDays(1)
                                    .atStartOfDay(zoneId)
                                    .with(LocalTime.of(2, 0));
        
        ZonedDateTime zdtResult = 
                Instant.ofEpochMilli(
                            ThreadLocalRandom
                            .current()
                            .nextLong(
                                        zdtStart.toInstant().toEpochMilli(), 
                                        zdtEnd.toInstant().toEpochMilli()
                                    )
                        ).atZone(zoneId);
        
        LocalTime time = zdtResult.toLocalTime();
        System.out.println(time);
    }
}

Learn more about the modern date-time API from Trail: Date Time.

ONLINE DEMO printing 100 random times.

Galactopoietic answered 3/4, 2021 at 12:4 Comment(6)
I suggest adding some explanation of how we cannot know how much time will elapse between 4 PM and 2 AM without applying a date and time zone. Your code is taking steps that, while correct and appropriate, may seem irrelevant to someone with a naïve misunderstanding of date-time handling.Adust
This is the correct, well explained and recommended answer. From the usually in the first sentence of the question I gather a requirement to take both cases into account (a) the restaurant closes before midnight, so the times are on the same day (b) the restaurant closes at or after midnight, the closing time is on the next day. Assuming today may not be correct. If one does not know the date, one cannot take summer time transitions and the like into account and will need a different approach.Edwardedwardian
@OleV.V. both the requirements you mentioned are correct. While I am able to generate a random time between opening and closing time for case (a), I wasn't able to do so for case (b).Burmeister
I am new to the concepts of ZonedDateTime, and Instant so understanding this code is a bit difficult for me right now.Burmeister
@VaibhavAgrawal A ZonedDateTime is a date and time in a time zone. If you want the time for a particular day and it needs to work correctly in all time zones, then you need this because it takes DST transitions and other time line anomalies into account. An Instant is a plain point in time (as the name says). The code converts from LocalTime to ZonedDateTime to Instant to milliseconds in order to do the math on the milliseconds values, then converts back to Instant to ZonedDateTime to LocalTime.Edwardedwardian
That's neat @OleV.V. I am getting it now..Thanks!! And the issue of origin less than bounds gets solved because EpochMilli returns the number of milliseconds from the epoch of 1970-01-01T00:00:00Z. Although 4 PM and 2 AM were just examples so rather than hardcoding values LocalTime.of(16, 0) instead restaurant.openingTime can be used. And ZonedDateTime zdtEnd = today.plusDays(1).atStartOfDay(zoneId).with(LocalTime.of(2, 0)); can be checked using if condition so that if closingTime falls within same day i.e. if (openingTime.isBefore(closingTime)) then plusDays(1) can be removed.Burmeister

© 2022 - 2024 — McMap. All rights reserved.