为选定的时间段和频率在两个日期之间生成随机时间的随机日期

时间:2020-12-30 18:00:40

标签: php algorithm date datetime email

我必须创建一个计划组件来计划需要发送的电子邮件。用户可以选择开始时间、结束时间和频率。代码应该在开始和结束时间之间为每个频率产生一个随机时刻。非办公时间。

参数:

用户可以选择 01/01/2020(开始)和 01/01/2021(结束)之间的时间段。在这种情况下,用户选择的时间跨度正好是一年。

用户可以选择频率。在这种情况下,用户选择“2 个月”。

功能:

代码生成日期时间列表。总时间(一年)除以频率(2 个月)。我们期望有 6 个日期时间的列表。

每个日期时间都是所述频率(2 个月)中的随机时刻。办公时间内。

结果:

这些参数的示例结果可能如下,为了清楚起见,计算出的频率范围:

  • [jan/feb] 21-02-2020 11.36
  • [mrt/apr] 04-03-2020 16.11
  • [mei/jun] 13-05-2020 09.49
  • [7 月至 8 月] 14-07-2020 15.25
  • [sep-okt] 02-09-2020 14.09
  • [十一月至十二月] 25-12-2020 13.55

--

我一直在考虑如何最好地实现这一点,但我想不出一个优雅的解决方案。

如何使用 PHP 做到这一点?

任何见解、参考或代码峰值将不胜感激。我真的坚持这个。

3 个答案:

答案 0 :(得分:2)

我认为您只是询问有关如何生成重复(每周 2 次)日期列表的建议,并且在上午 9 点到下午 5 点之间具有随机时间?是吗?

如果是这样 - 像这样的东西(未经测试的伪代码)可能是一个起点:

$start    = new Datetime('1st January 2021');
$end      = new Datetime('1st July 2021');
$day_start = 9;
$day_end = 17;

$date = $start;
$dates = [$date]; // Start date into array
while($date < $end) {
            
    $new_date = clone($date->modify("+ 2 weeks"));
    $new_date->setTime(mt_rand($day_start, $day_end), mt_rand(0, 59));
        
    $dates[] = $new_date;
}

var_dump($dates);

答案 1 :(得分:2)

Steve 的 anwser 看起来不错,但您应该考虑另外两件事

  1. 假期检查,在第一个 $new_date 行之后的 while 中,例如:
<块引用>
$holiday = array('2021-01-01',    '2021-01-06', '2021-12-25');
if (!in_array($new_date,$holiday))
  1. 还以与上述类似的方式将工作日作为数组检查日期是工作日还是周末。

答案 2 :(得分:1)

这是一种蹩脚的代码,但我认为它会如你所愿。

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

如我在示例中所示,您必须通过 $start$end$frequencyInSeconds,然后您将获得随机日期。请注意, I $start$end 中必须包含小时数,因为它们用作轮班的开始时间和结束时间。因为规则是只在工作日返回一个班次时间内的日期。此外,您必须以秒为单位提供频率 - 您可以在函数外部计算它们,也可以更改它以在内部计算它们。我这样做是因为我不知道你预定义的时期是什么。

此函数返回一组 \DateTime() 实例,因此您可以对它们执行任何操作。

2020 年 8 月 1 日更新: 现在假期是计算的一部分,如果在调用函数时传递了它们,它们将被排除在返回的日期之外。由于复活节等假期,您可以以 d/md/m/Y 格式传递它们,如果假期是周末,但人们会在工作周获得额外的休息日。

2020 年 1 月 13 日更新:$frequencyInSeconds 短于 1 天时,我已更新代码版本以解决无限循环问题。新代码使用很少的函数 getDiffInSecondsgetShiftDatadayIsWeekendOrHoliday 作为辅助方法来减少代码重复和更清晰可读的代码

相关问题