skip to Main Content

A bit similar to this one, yet different.

I need to generate two random dates:

  • First between $startDate and $endDate
  • Second between generated first date and $endDate
  • Both with random hour falling between $startHour and $endHour.

I am doing this like that:


$createDate = new DateTime();
$updateDate = new DateTime();

$createDate
    ->setTimestamp(rand($startDate, $endDate))
    ->setTime(rand($startHour, $endHour), rand(0, 59), rand(0, 59));

$updateDate
    ->setTimestamp(rand($createDate->getTimestamp(), $endDate))
    ->setTime(rand($startHour, $endHour), rand(0, 59), rand(0, 59));

How to modify the above code to assure that both createDate and updateDate does not fall on Sunday?

3

Answers


  1. Try this:

    <?php
    
    function generateRandomDateExcludingSunday() {
        function getRandomDayOfWeek() {
            return rand(1, 7);
        }
    
        function isSunday($dayOfWeek) {
            return $dayOfWeek == 7;
        }
    
        do {
            $randomDayOfWeek = getRandomDayOfWeek();
        } while (isSunday($randomDayOfWeek));
    
        $currentDate = new DateTime();
        $currentDayOfWeek = $currentDate->format('N');
        $dayDifference = $randomDayOfWeek - $currentDayOfWeek;
        $randomDate = $currentDate->modify("+$dayDifference days");
    
        echo "Random date excluding Sunday: " . $randomDate->format('Y-m-d') . PHP_EOL;
    }
    
    generateRandomDateExcludingSunday();
    
    ?>
    

    Try add function, and no variables.

    Login or Signup to reply.
  2. How to modify the above code to assure that both createDate and updateDate does not fall on Sunday?

    I modify your code to this:

    $createDate = new DateTime();
    $updateDate = new DateTime();
    
    do {
       $createDate
           ->setTimestamp(rand($startDate, $endDate))
           ->setTime(rand($startHour, $endHour), rand(0, 59), rand(0, 59));
    } while ($createDate->format('N') == 7); // Repeat until createDate is not a Sunday
    
    do {
       $updateDate
           ->setTimestamp(rand($createDate->getTimestamp(), $endDate))
           ->setTime(rand($startHour, $endHour), rand(0, 59), rand(0, 59));
    } while ($updateDate->format('N') == 7); // Repeat until updateDate is not a Sunday
    

    Here, I used do...while loop to keep generating a random date until the generated date is not a Sunday. The format('N') is used to returns the day of the week as an integer, and 7 represents Sunday. The loop will continue until format('N') does not return 7, ensuring that the generated date is not a Sunday*.

    *Note that this may be inefficient if the range of dates between $startDate and $endDate includes many Sundays, as it may take several iterations to find a date that is not a Sunday. In such, you may want to consider generating a list of all weekdays within the date range and then randomly selecting from that list, as answered by @waterloomatt here.

    Additional

    Generally "random" + "loop" = "infinite loop, or at least takes forever sometimes"

    Add counter for a maximum number of attempts to prevent the loop from running indefinitely.

    $createDate = new DateTime();
    $updateDate = new DateTime();
    
    $maxAttempts = 1000; // Maximum number of attempts to generate a non-Sunday date
    $attempts = 0; // Counter for the number of attempts
    
    do {
       $createDate
           ->setTimestamp(rand($startDate, $endDate))
           ->setTime(rand($startHour, $endHour), rand(0, 59), rand(0, 59));
       $attempts++;
    } while ($createDate->format('N') == 7 && $attempts < $maxAttempts); // Repeat until createDate is not a Sunday
    
    $attempts = 0; // Reset the counter for the next date
    
    do {
       $updateDate
           ->setTimestamp(rand($createDate->getTimestamp(), $endDate))
           ->setTime(rand($startHour, $endHour), rand(0, 59), rand(0, 59));
       $attempts++;
    } while ($updateDate->format('N') == 7 && $attempts < $maxAttempts); // Repeat until updateDate is not a Sunday
    

    *The above may still has a possibility of infinite loop if the range of possible dates only includes Sundays and the maximum number of attempts is reached, so you could add additional logic to adjust the range of possible dates or handle the case where no non-Sunday dates are available.

    Login or Signup to reply.
  3. The trouble with the other answers is that having your loop bounded by a random condition can put you in the situation where the loop sometimes takes an extremely long time to complete, or puts itself into a state where it can never complete.

    A constant-time solution would be something like the below where we simply generate a list of valid dates, and then pick randomly from those according to the rules set.

    <?php
    
    function daysBetween(
        DateTimeInterface $start,
        DateTimeInterface $end,
        array $filters = [],
        DateInterval $interval = new DateInterval('P1D')
    ) {
        for( $cur = $start; $cur <= $end; $cur = (clone $cur)->add($interval) ) {
            foreach( $filters as $filter ) {
                if( ! $filter($cur) ) {
                    continue 2;
                }
            }
            yield $cur;
        }
    }
    
    $tz      = new DateTimezone('UTC');
    $start   = new DateTimeImmutable('2024-01-01', $tz);
    $end     = new DateTimeImmutable('2024-01-31', $tz);
    $filters = [ function($a){return $a->format('N') != 7;}];
    
    $startHour = 9;
    $endHour   = 17;
    
    // generate a filtered list of days
    $days = iterator_to_array(daysBetween($start, $end, $filters));
    $dayCount = count($days);
    
    // pick indexes for the dates in between
    $createIndex = rand(1, $dayCount-3);
    $updateIndex = rand($createIndex + 1, $dayCount - 2);
    
    $create = $days[$createIndex]->setTime(rand($startHour, $endHour), rand(0, 59), rand(0, 59));;
    $update = $days[$updateIndex]->setTime(rand($startHour, $endHour), rand(0, 59), rand(0, 59));;
    
    $fmt = 'D Y-m-d H:i';
    
    printf(<<<_E_
    Start:  %s
    Create: %s
    Update: %s
    End:    %s
    _E_
    ,
        $start->format($fmt),
        $create->format($fmt),
        $update->format($fmt),
        $end->format($fmt)
    );
    

    Output:

    Start:  Mon 2024-01-01 00:00
    Create: Sat 2024-01-27 14:29
    Update: Tue 2024-01-30 13:32
    End:    Wed 2024-01-31 00:00
    

    Note that the upper/lower bounds for the create/update indexes are based on the assumption that neither can overlap with the start/end dates.

    It should also be possible to approach the "no sundays" rule in a purely mathematical way based on the start/end and calculate the indices/offsets that way, but I’m just not feeling very math-y today.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search