Заметки
Как в БП сделать отрицательную разницу дат
Почему datediff не всегда подходит для знаковой разницы дат в бизнес-процессах Bitrix24 и как посчитать отрицательный интервал через PHP-активити.
datediff хорошо подходит, когда нужно просто вывести разницу между датами.
Но если важен знак — дата раньше или позже другой даты, — проще и надёжнее посчитать
разницу в секундах через PHP-активити и сохранить результат в переменную.
Задача
Иногда в бизнес-процессе нужно не просто понять, сколько дней между двумя датами, а получить отрицательное или положительное значение.
Например:
- если дедлайн уже прошёл — получить
-3дня; - если до дедлайна осталось время — получить
2дня; - если даты совпадают — получить
0; - если нужно сортировать или сравнивать элементы по отклонению от даты.
Обычная разница без знака здесь не подходит. Для таких сценариев важно не только количество дней, но и направление: первая дата раньше второй или позже.
Почему datediff неудобен
В выражениях БП для разницы дат обычно используют datediff.
Например, так можно получить количество дней между датой создания и текущим моментом.
{{=datediff({{Дата создания элемента}}, {=System:NowLocal}, "%a")}}
Для простых проверок этого достаточно. Но когда нужно получить отрицательное значение,
datediff становится неудобным.
Причина в том, что datediff — это функция форматирования интервала.
Она хорошо отвечает на вопрос «какая разница между датами», но плохо подходит
для прикладной логики вида «верни мне -5, если дата уже в прошлом».
Можно пытаться строить сложные выражения через if, сравнение дат и несколько
вызовов datediff. Но такие формулы быстро становятся нечитаемыми.
Их неудобно отлаживать, особенно если участвуют поля Дата,
Дата/время и разные часовые пояса.
Поэтому для знаковой разницы лучше считать не форматированный интервал, а обычную математику: перевести обе даты во время, вычесть одно из другого и получить число.
Решение через PHP-активити
В PHP-активити можно взять две даты, привести их к timestamp, посчитать разницу в секундах и сохранить итог в переменную бизнес-процесса.
В примере ниже считаем разницу в днях между двумя датами. Если вторая дата позже первой, результат будет положительным. Если раньше — отрицательным.
<?php
use Bitrix\Main\Type\Date;
use Bitrix\Main\Type\DateTime;
const SECONDS_IN_DAY = 86400;
$first_date_raw = '{{Дата начала}}';
$second_date_raw = '{{Дата окончания}}';
$first_timestamp = getTimestampFromBizprocDate($first_date_raw);
$second_timestamp = getTimestampFromBizprocDate($second_date_raw);
if ($first_timestamp === null || $second_timestamp === null) {
$this->SetVariable('DateDiffDays', '');
return;
}
$difference_seconds = $second_timestamp - $first_timestamp;
$difference_days = (int)floor($difference_seconds / SECONDS_IN_DAY);
$this->SetVariable('DateDiffDays', $difference_days);
/**
* Приводит дату из бизнес-процесса к timestamp.
*/
function getTimestampFromBizprocDate(string $date_raw): ?int
{
$date_raw = trim($date_raw);
if ($date_raw === '') {
return null;
}
try {
if (preg_match('/\d{1,2}:\d{2}/', $date_raw) === 1) {
return DateTime::createFromUserTime($date_raw)->getTimestamp();
}
return Date::createFromUserTime($date_raw)->getTimestamp();
} catch (Throwable $exception) {
return null;
}
}
В переменной DateDiffDays после этого будет обычное число:
-3, 0, 5 и так далее.
Знак зависит от порядка вычитания: в примере считается
вторая дата - первая дата. Если поменять даты местами, знак
тоже поменяется. abs здесь использовать не нужно: он как раз
уберёт отрицательное значение.
Такой результат уже удобно использовать в условиях БП: проверить, меньше ли значение нуля, записать число в поле, вывести в уведомлении или использовать для дальнейших расчётов.
Если нужно считать не «полные дни вниз», а округлять к ближайшему дню, замените
floor на round. Если нужно отбрасывать дробную часть к нулю,
можно использовать обычное приведение к int.
Пустую дату лучше обрабатывать отдельно, а не превращать в 0.
Если дата приходит в нестандартном формате, сначала выведите её в журнал БП
и посмотрите, какая строка попадает в PHP-код.
Вариант с часами и минутами
Иногда дни слишком грубые. Например, нужно понять, на сколько часов просрочен дедлайн или сколько минут осталось до контрольного времени.
Тогда можно сохранить сразу несколько переменных: секунды, минуты, часы и дни.
<?php
use Bitrix\Main\Type\Date;
use Bitrix\Main\Type\DateTime;
const SECONDS_IN_MINUTE = 60;
const SECONDS_IN_HOUR = 3600;
const SECONDS_IN_DAY = 86400;
$start_date_raw = '{{Дата начала}}';
$target_date_raw = '{{Контрольная дата}}';
$start_timestamp = getTimestampFromBizprocDate($start_date_raw);
$target_timestamp = getTimestampFromBizprocDate($target_date_raw);
if ($start_timestamp === null || $target_timestamp === null) {
$this->SetVariable('DateDiffSeconds', '');
$this->SetVariable('DateDiffMinutes', '');
$this->SetVariable('DateDiffHours', '');
$this->SetVariable('DateDiffDays', '');
return;
}
$difference_seconds = $target_timestamp - $start_timestamp;
$this->SetVariable('DateDiffSeconds', $difference_seconds);
$this->SetVariable('DateDiffMinutes', (int)floor($difference_seconds / SECONDS_IN_MINUTE));
$this->SetVariable('DateDiffHours', (int)floor($difference_seconds / SECONDS_IN_HOUR));
$this->SetVariable('DateDiffDays', (int)floor($difference_seconds / SECONDS_IN_DAY));
/**
* Приводит дату из бизнес-процесса к timestamp.
*/
function getTimestampFromBizprocDate(string $date_raw): ?int
{
$date_raw = trim($date_raw);
if ($date_raw === '') {
return null;
}
try {
if (preg_match('/\d{1,2}:\d{2}/', $date_raw) === 1) {
return DateTime::createFromUserTime($date_raw)->getTimestamp();
}
return Date::createFromUserTime($date_raw)->getTimestamp();
} catch (Throwable $exception) {
return null;
}
}
После этого можно отдельно использовать DateDiffMinutes,
DateDiffHours или DateDiffDays. Знак сохранится во всех
значениях.
Например, если контрольная дата была на два часа раньше стартовой,
DateDiffHours будет отрицательным.