How to Generate Random Date Between Two Dates Using PHP

How to generate random date between two dates using php?

PHP has the rand() function:

$int= rand(1262055681,1262055681);

It also has mt_rand(), which is generally purported to have better randomness in the results:

$int= mt_rand(1262055681,1262055681);

To turn a timestamp into a string, you can use date(), ie:

$string = date("Y-m-d H:i:s",$int);

Getting random date between two dates

Use rand():

 $random = Carbon::now()->subMinutes(rand(1, 55));

Generate random dates with random times between two dates for selected period and frequency

It's kind of crappy code but I think it will work as you wish.

function getDiffInSeconds(\DateTime $start, \DateTime $end) : int
{
$startTimestamp = $start->getTimestamp();
$endTimestamp = $end->getTimestamp();

return $endTimestamp - $startTimestamp;
}

function getShiftData(\DateTime $start, \DateTime $end) : array
{
$shiftStartHour = \DateTime::createFromFormat('H:i:s', $start->format('H:i:s'));
$shiftEndHour = \DateTime::createFromFormat('H:i:s', $end->format('H:i:s'));

$shiftInSeconds = intval($shiftEndHour->getTimestamp() - $shiftStartHour->getTimestamp());

return [
$shiftStartHour,
$shiftEndHour,
$shiftInSeconds,
];
}

function dayIsWeekendOrHoliday(\DateTime $date, array $holidays = []) : bool
{
$weekendDayIndexes = [
0 => 'Sunday',
6 => 'Saturday',
];

$dayOfWeek = $date->format('w');
if (empty($holidays)) {
$dayIsWeekendOrHoliday = isset($weekendDayIndexes[$dayOfWeek]);
} else {
$dayMonthDate = $date->format('d/m');
$dayMonthYearDate = $date->format('d/m/Y');
$dayIsWeekendOrHoliday = (isset($weekendDayIndexes[$dayOfWeek]) || isset($holidays[$dayMonthDate]) || isset($holidays[$dayMonthYearDate]));
}

return $dayIsWeekendOrHoliday;
}

function getScheduleDates(\DateTime $start, \DateTime $end, int $frequencyInSeconds) : array
{
if ($frequencyInSeconds < (24 * 60 * 60)) {
throw new \InvalidArgumentException('Frequency must be bigger than one day');
}

$diffInSeconds = getDiffInSeconds($start, $end);

// If difference between $start and $end is bigger than two days
if ($diffInSeconds > (2 * 24 * 60 * 60)) {
// If difference is bigger than 2 days we add 1 day to start and subtract 1 day from end
$start->modify('+1 day');
$end->modify('-1 day');

// Getting new $diffInSeconds after $start and $end changes
$diffInSeconds = getDiffInSeconds($start, $end);
}

if ($frequencyInSeconds > $diffInSeconds) {
throw new \InvalidArgumentException('Frequency is bigger than difference between dates');
}

$holidays = [
'01/01' => 'New Year',
'18/04/2020' => 'Easter 1st official holiday because 19/04/2020',
'20/04/2020' => 'Easter',
'21/04/2020' => 'Easter 2nd day',
'27/04' => 'Konings',
'04/05' => '4mei',
'05/05' => '4mei',
'24/12' => 'Christmas 1st day',
'25/12' => 'Christmas 2nd day',
'26/12' => 'Christmas 3nd day',
'27/12' => 'Christmas 3rd day',
'31/12' => 'Old Year'
];

[$shiftStartHour, $shiftEndHour, $shiftInSeconds] = getShiftData($start, $end);
$amountOfNotifications = floor($diffInSeconds / $frequencyInSeconds);
$periodInSeconds = intval($diffInSeconds / $amountOfNotifications);
$maxDaysBetweenNotifications = intval($periodInSeconds / (24 * 60 * 60));
// If $maxDaysBetweenNotifications is equals to 1 then we have to change $periodInSeconds to amount of seconds for one day
if ($maxDaysBetweenNotifications === 1) {
$periodInSeconds = (24 * 60 * 60);
}

$dates = [];
for ($i = 0; $i < $amountOfNotifications; $i++) {
$periodStart = clone $start;
$periodStart->setTimestamp($start->getTimestamp() + ($i * $periodInSeconds));
$seconds = mt_rand(0, $shiftInSeconds);

// If $maxDaysBetweenNotifications is equals to 1 then we have to check only one day without loop through the dates
if ($maxDaysBetweenNotifications === 1) {
$interval = new \DateInterval('P' . $maxDaysBetweenNotifications . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);

$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);
} else {
// When $maxDaysBetweenNotifications we have to loop through the dates to pick them
$loopsCount = 0;
$maxLoops = 3; // Max loops before breaking and skipping the period
do {
$day = mt_rand(0, $maxDaysBetweenNotifications);
$periodStart->modify($shiftStartHour);
$interval = new \DateInterval('P' . $day . 'DT' . $seconds . 'S');
$date = clone $periodStart;
$date->add($interval);

$dayIsWeekendOrHoliday = dayIsWeekendOrHoliday($date, $holidays);

// If the day is weekend or holiday then we have to increment $loopsCount by 1 for each loop
if ($dayIsWeekendOrHoliday === true) {
$loopsCount++;

// If $loopsCount is equals to $maxLoops then we have to break the loop
if ($loopsCount === $maxLoops) {
break;
}
}
} while ($dayIsWeekendOrHoliday);
}

// Adds the date to $dates only if the day is not a weekend day and holiday
if ($dayIsWeekendOrHoliday === false) {
$dates[] = $date;
}
}

return $dates;
}

$start = new \DateTime('2020-12-30 08:00:00', new \DateTimeZone('Europe/Sofia'));
$end = new \DateTime('2021-01-18 17:00:00', new \DateTimeZone('Europe/Sofia'));
$frequencyInSeconds = 86400; // 1 day

$dates = getScheduleDates($start, $end, $frequencyInSeconds);
var_dump($dates);

You have to pass $start, $end and $frequencyInSeconds as I showed in example and then you will get your random dates. Notice that I $start and $end must have hours in them because they are used as start and end hours for shifts. Because the rule is to return a date within a shift time only in working days. Also you have to provide frequency in seconds - you can calculate them outside the function or you can change it to calculate them inside. I did it this way because I don't know what are your predefined periods.

This function returns an array of \DateTime() instances so you can do whatever you want with them.

UPDATE 08/01/2020:
Holidays now are part of calculation and they will be excluded from returned dates if they are passed when you are calling the function. You can pass them in d/m and d/m/Y formats because of holidays like Easter and in case when the holiday is on weekend but people will get additional dayoff during the working week.

UPDATE 13/01/2020:
I've made updated code version to fix the issue with infinite loops when $frequencyInSeconds is shorter like 1 day. The new code used few functions getDiffInSeconds, getShiftData and dayIsWeekendOrHoliday as helper methods to reduce code duplication and cleaner and more readable code

PHP to generate random date

You could do it with mt_rand() like this:

echo date('Y-m-d')." ".mt_rand(0, 23).":".mt_rand(0, 59).":".mt_rand(0, 59);

Random time and date between 2 date values

Easy :) Just choose 2 random dates, convert to EPOCH, and random between these 2 values :)

EPOCH - The time since 1/1/1970, in seconds.

You can use the strtotime() function to make date-strings turn into epoch time, and the date() function to make it the other way back.

function rand_date($min_date, $max_date) {
/* Gets 2 dates as string, earlier and later date.
Returns date in between them.
*/

$min_epoch = strtotime($min_date);
$max_epoch = strtotime($max_date);

$rand_epoch = rand($min_epoch, $max_epoch);

return date('Y-m-d H:i:s', $rand_epoch);
}

PHP random date and multiple dates

Simply add random number of hours to your string for strtotime, can do same for minutes

With Hours

<?php 
echo date('Y-m-d H:i', strtotime( '+'.mt_rand(0,31).' days '.mt_rand(9,17). 'Hours' ));
?>

With Hours and Minutes

<?php 
echo date('Y-m-d H:i', strtotime( '+'.mt_rand(0,31).' days '.mt_rand(9,17). 'Hours '.mt_rand(0,30). 'Minutes' ));
?>

Get random day, month, year, day of the week

Lets hold this short, and here's a link with kinda same question

with mt_rand you can generate random timestamps between those two
use Unix Timestamps for that
then convert that timestamp with the date function for either Day, Month, Year etc.

here's an example:

$int= mt_rand(1232055681,1262055681);
$string = date("Y-m-d H:i:s",$int);
echo "<p>$string</p><br>";

hope this helps and welcome! :)

Generate 50 random-duration time intervals in MySQL or PHP, between two dates

Yes, you can do this in MySQL. The general algorithm for N intervals is:

  1. Generate N-1 distinct, random timestamps between your begin time + 1 second and your end time - 2 seconds.
  2. Sort them, oldest to youngest. These are your interval begin points.
  3. Subtract one second to from each end point to get the prior interval's start point.

Example
Install these generator views, then:

CREATE TABLE times (id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,begin_time DOUBLE NOT NULL,end_time DOUBLE NOT NULL DEFAULT 0);
INSERT INTO times (begin_time) SELECT @low bp UNION SELECT s.bp FROM (SELECT t.bp FROM (SELECT @low + FLOOR(RAND() * (@high-@low)) bp FROM generator_256 JOIN (SELECT @low := UNIX_TIMESTAMP('2014-09-01 00:00:00'), @high := UNIX_TIMESTAMP('2014-10-12 23:59:59')) init LIMIT 49) t ORDER by bp) s;
UPDATE times JOIN (SELECT curr.id, curr.begin_time, (SELECT next.begin_time-1 FROM times next WHERE next.id=curr.id+1) end_time FROM times curr) g ON g.id = times.id SET times.end_time = COALESCE(g.end_time, UNIX_TIMESTAMP('2014-10-12 23:59:59'));
SELECT FROM_UNIXTIME(begin_time), FROM_UNIXTIME(end_time) FROM times;
+---------------------------+-------------------------+
| FROM_UNIXTIME(begin_time) | FROM_UNIXTIME(end_time) |
+---------------------------+-------------------------+
| 2014-09-01 00:00:00 | 2014-09-02 13:32:45 |
| 2014-09-02 13:32:46 | 2014-09-03 07:57:24 |
| 2014-09-03 07:57:25 | 2014-09-04 17:34:01 |
| 2014-09-04 17:34:02 | 2014-09-04 19:46:25 |
| 2014-09-04 19:46:26 | 2014-09-05 17:44:48 |
...
| 2014-10-10 18:39:47 | 2014-10-11 05:11:13 |
| 2014-10-11 05:11:14 | 2014-10-11 11:27:29 |
| 2014-10-11 11:27:30 | 2014-10-12 13:03:02 |
| 2014-10-12 13:03:03 | 2014-10-12 17:55:54 |
| 2014-10-12 17:55:55 | 2014-10-12 19:11:11 |
| 2014-10-12 19:11:12 | 2014-10-12 23:59:59 |
+---------------------------+-------------------------+
50 rows in set (0.00 sec)

Explanation
Let's break these down, step by step. To generate rows in MySQL, you have to use a generator view. A generator view just gives you N rows each time you ask for it. As an example, to get 49 rows (N-1):

SELECT * FROM generator_256 LIMIT 49;

To generate a single random number between two other numbers in MySQL, use the random number formula low + (RAND() * (high-low)). This formula combined with the generator view gets us the 49 begin points we want for step 1:

SELECT (@low + FLOOR(RAND() * (@high-@low))) AS bp FROM generator_256 LIMIT 49;

(I'm using session variables here to keep the SQL simple. They'll become part of the query in a just a bit. If you want to debug, remember bp is a timestamp, so FROM_UNIXTIME(bp) will show you a human-friendly format.)

Now, to sort the list, use a sub-query: if you sort the generated query, you'll get random values clustered near the beginning time. So, to mostly fulfill step 2:

SELECT t.bp FROM (SELECT @low + FLOOR(RAND() * (@high-@low)) bp FROM generator_256 LIMIT 49) t ORDER by t.bp;

Now, it starts to get tricky. For any given row, we want to fill the end time with one second less than the next row's beginning time. While there are a couple of ways of going about it, I think the cleanest to understand is one that uses the destination table (or a copy of it) to store our generated begin points. (Note I've initialized values for @low and @high here as well as included the beginning point in the list):

CREATE TABLE times (id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,begin_time DOUBLE NOT NULL,end_time DOUBLE NOT NULL DEFAULT 0);
INSERT INTO times (begin_time) SELECT @low bp UNION SELECT s.bp FROM (SELECT t.bp FROM (SELECT @low + FLOOR(RAND() * (@high-@low)) bp FROM generator_256 JOIN (SELECT @low := UNIX_TIMESTAMP('2014-09-01 00:00:00'), @high := UNIX_TIMESTAMP('2014-10-12 23:59:59')) init LIMIT 49) t ORDER by bp) s;

Finally, we can add the end times using a joined update.

UPDATE times JOIN (SELECT curr.id, curr.begin_time, (SELECT next.begin_time-1 FROM times next WHERE next.id=curr.id+1) end_time FROM times curr) g ON g.id = times.id SET times.end_time = COALESCE(g.end_time, UNIX_TIMESTAMP('2014-10-12 23:59:59'));

In my examples here, I've left off two things:

  1. I'm ignoring the need to start one second after the begin and two seconds prior to the end. Thus, it's possible that one of your random values could equal the begin or end point, which would violate your stated need. You can add these constraints by using the MySQL INTERVAL operator.
  2. In a truely random system, a long sequence of identical values is just as likely as a jumbled one. Eg, in binary, 000000000000000 is just as likely as 111111111111111 is just as likely as 0101010111101011. That means we could get 50 identical dates out of our query. You can get around this by generating a whole bunch of random times (1000s, maybe) and culling from that.

How can I generate a random date in the past?

unix timestamp is an integer from 0 to n so you can just use the normal random method in php :)

$timestamp = rand(0, time() - 60*60*24*365*10);

// Prints something like: Monday 8th of August 2005 03:12:46 PM
echo date('l jS \of F Y h:i:s A', $timestamp);


Related Topics



Leave a reply



Submit