#php #algorithm #date #datetime #email
#php #алгоритм #Дата #дата и время #Адрес электронной почты
Вопрос:
Мне нужно создать компонент планирования, который будет планировать электронные письма, которые необходимо отправить. Пользователи могут выбирать время начала, время окончания и частоту. Код должен генерировать случайный момент для каждой частоты, между временем начала и окончания. В нерабочее время.
Параметры:
Пользователь может выбрать период между 01/01/2020 (начало) и 01/01/2021 (конец). В этом случае пользователь выбирает интервал времени ровно в один год.
Пользователь может выбрать частоту. В этом случае пользователь выбирает «2 месяца».
Функция:
Код создает список дат. Общее время (один год) делится на частоту (2 месяца). Мы ожидаем список из 6 дат.
Каждое время даты является случайным моментом в указанной частоте (2 месяца). В рабочее время.
Результат:
Примерный результат для этих параметров может быть следующим, с рассчитанными частотными границами для наглядности:
- [январь / февраль] 21-02-2020 11.36
- [mrt / apr] 04-03-2020 16.11
- [мэй / июнь] 13-05-2020 09.49
- [июль-август] 14-07-2020 15.25
- [сентябрь-окт] 02-09-2020 14.09
- [ноябрь-декабрь] 25-12-2020 13.55
—
Я думал о том, как реализовать это наилучшим образом, но я не могу найти элегантного решения.
Как это можно сделать с помощью PHP?
Любые идеи, ссылки или пики кода будут высоко оценены. Я действительно застрял на этом.
Ответ №1:
Я думаю, вы просто просите предложения о том, как сгенерировать список повторяющихся (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 недели с даты начала. Я ищу случайный день и время в течение этих двух недель. Я обновлю исходный вопрос. Спасибо за ваше время, оно ценится.
Ответ №2:
Ответ Стива кажется хорошим, но вы должны рассмотреть 2 дополнительные вещи
- проверка праздничных дней в while после первой строки $new_date, например:
$holiday = array('2021-01-01', '2021-01-06', '2021-12-25');
if (!in_array($new_date,$holiday))
- также проверьте, является ли дата рабочим днем или выходным, аналогично приведенному выше, с рабочими днями в виде массива.
Комментарии:
1. Хорошее предложение — но нужно быть осторожным при выполнении проверки in_array, поскольку $new_date будет объектом DateTime, а не строкой. Может быть связано с if(!in_array($new_date->format(‘Y-m-d’), $holiday)) хотя.
2. Умно с вашей стороны указать праздники. Я посмотрю, как я буду с этим справляться. Пожалуйста, ознакомьтесь с обновленным вопросом, если хотите, я лучше объяснил, что я имею в виду под частотой.
3. в наши дни вы должны делать это как объект — это факт. на данный момент я имею в виду только математический расчетный метод с некоторым случайным измененным временем. Некоторое время назад у меня была аналогичная проблема с праздничными днями для управления временем в моем приложении. Но сделал это без объекта и простого массива.
Ответ №3:
Это довольно дерьмовый код, но я думаю, что он будет работать так, как вы пожелаете.
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
, как я показал в примере, и тогда вы получите свои случайные даты. Обратите внимание, что у меня $start
и $end
должны быть часы в них, потому что они используются в качестве начальных и конечных часов для смен. Потому что правило должно возвращать дату в течение времени смены только в рабочие дни. Также вы должны указать частоту в секундах — вы можете вычислить их вне функции или изменить ее, чтобы вычислить их внутри. Я сделал это так, потому что я не знаю, каковы ваши предопределенные периоды.
Эта функция возвращает массив экземпляров DateTime(), чтобы вы могли делать с ними все, что захотите.
ОБНОВЛЕНИЕ 01.08.2020: праздничные дни теперь являются частью вычисления, и они будут исключены из возвращаемых дат, если они передаются при вызове функции. Вы можете передавать их в d/m
d/m/Y
форматах и из-за праздников, таких как Пасха, и в случае, когда праздник приходится на выходные, но люди получат дополнительный выходной в течение рабочей недели.
ОБНОВЛЕНИЕ 13/01/2020: я создал обновленную версию кода, чтобы устранить проблему с бесконечными циклами, когда $frequencyInSeconds
они короче, чем 1 день. В новом коде использовалось несколько функций getDiffInSeconds
, getShiftData
а dayIsWeekendOrHoliday
также вспомогательные методы для уменьшения дублирования кода и создания более чистого и читаемого кода
Комментарии:
1. Супер! Мы приближаемся! Как это связано с 28/30/31-дневными месяцами и праздниками? Спасибо за ваше время и усилия!
2. Я не думаю, что с этим есть проблемы — сначала около 28/30/31 — как вы можете видеть здесь
$interval = new DateInterval('P' . $day . 'DT' . $seconds . 'S');
, я установил интервал в дни и секунды, которые добавляются к дате клонированного периода здесь$date->add($interval);
с помощью php DateTime класс будет обрабатывать точную дату, и это будет 100% действительная дата 🙂 О праздниках — видите ли вы подобное требование в своем вопросе? Позже я обновлю свой пост, добавив возможность исключать праздники из сгенерированных дат.3. @user2863494 добавлена обработка праздников, и мой ответ был обновлен
4. Спасибо за ваше время и усилия. Это выглядит хорошо! Я наградил награду за вашу работу. Я проведу дальнейшее тестирование с несколькими входными параметрами и, соответственно, приму ваш ответ как правильный.
5. @user2863494 пожалуйста, отметьте ответ как «принятый ответ»