← Назад к заметкам

Заметки

Как в БП сделать отрицательную разницу дат

Почему 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 будет отрицательным.