Reputation: 17505
A bit similar to this one, yet different.
I need to generate two random dates:
$startDate
and $endDate
$endDate
$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?
As you can figure out from the above code, the $startDate
and $endDate
are integers and contains timestamps. The $startHour
and $endHour
are also integers, but contains pure hour numeral only.
Upvotes: 0
Views: 146
Reputation: 32272
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.
Upvotes: 3
Reputation: 2795
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.
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.
Upvotes: 2
Reputation: 54
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.
Upvotes: 1