Как красиво проверить вхождение даты (день и месяц) в интервал дат?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработчика 1С часто возникает задача: проверить, попадает ли конкретный день в определенный сезонный или циклический интервал. Примером может служить расчет праздничных скидок, проверка знаков зодиака или определение периода действия сезонных тарифов. Главная сложность здесь заключается в том, что нам не важен конкретный год — важны только день и месяц. Ситуация осложняется, когда интервал «перепрыгивает» через новый год, например, с 1 ноября по 1 марта.

В этой статье мы подробно разберем несколько изящных способов решения этой задачи, проанализируем их плюсы и минусы, а также выясним, как правильно обрабатывать такие нюансы, как високосные годы.

Метод 1: Приведение к фиксированному году

Это самый наглядный и интуитивно понятный способ. Мы берем проверяемую дату, начало и конец интервала и принудительно устанавливаем им один и тот же год. Однако здесь есть важный нюанс с високосным годом. Если мы выберем обычный год, то не сможем корректно обработать 29 февраля.

Проанализируем ситуацию: какой год лучше всего выбрать? Оптимальным вариантом является 2000 год, так как он високосный. Если мы будем использовать 0001 год, как часто советуют новички, платформа выдаст ошибку при попытке записать дату 29.02.0001.

Рассмотрим пример кода, реализующего этот подход:


Функция ДатаВходитВИнтервал(ПроверяемаяДата, ДатаНачала, ДатаКонца)
    
    // Приводим все даты к 2000 году для корректной обработки 29 февраля
    ЭталонныйГод = 2000;
    
    ПровДата = Дата(ЭталонныйГод, Месяц(ПроверяемаяДата), День(ПроверяемаяДата));
    Начало   = Дата(ЭталонныйГод, Месяц(ДатаНачала), День(ДатаНачала));
    Конец    = Дата(ЭталонныйГод, Месяц(ДатаКонца), День(ДатаКонца));
    
    Если Начало <= Конец Тогда
        // Обычный интервал внутри одного года
        Возврат (ПровДата >= Начало) И (ПровДата <= Конец);
    Иначе
        // Интервал с переходом через Новый год (например, с декабря по январь)
        Возврат (ПровДата >= Начало) ИЛИ (ПровДата <= Конец);
    КонецЕсли;

КонецФункции

Этот метод хорош тем, что он использует стандартные механизмы сравнения дат 1С и легко читается другими разработчиками.

Метод 2: Математическое преобразование (Числовой формат)

Если нам нужно выполнить проверку быстро, например, внутри запроса или в цикле с большим количеством данных, операции с объектами типа Дата могут быть избыточными. В таком случае мы можем перевести комбинацию дня и месяца в обычное число по формуле: Месяц * 100 + День.

Посмотрим, как это работает на примере. Дата 9 июля превратится в 7 * 100 + 9 = 709. А 1 октября превратится в 1001. Сравнение простых целых чисел выполняется процессором и СУБД значительно быстрее.

Разберем реализацию этого алгоритма:


ЧислоПров = Месяц(ПроверяемаяДата) * 100 + День(ПроверяемаяДата);
ЧислоНач  = Месяц(ДатаНачала) * 100 + День(ДатаНачала);
ЧислоКон  = Месяц(ДатаКонца) * 100 + День(ДатаКонца);

Если ЧислоНач <= ЧислоКон Тогда
    Результат = (ЧислоПров >= ЧислоНач И ЧислоПров <= ЧислоКон);
Иначе
    // Обработка перехода через год
    Результат = (ЧислоПров >= ЧислоНач ИЛИ ЧислоПров <= ЧислоKon);
КонецЕсли;

В языке запросов 1С это будет выглядеть следующим образом:


ВЫБРАТЬ
    ВЫБОР
        КОГДА МЕСЯЦ(&Начало) * 100 + ДЕНЬ(&Начало) <= МЕСЯЦ(&Конец) * 100 + ДЕНЬ(&Конец)
            ТОГДА МЕСЯЦ(&Дата) * 100 + ДЕНЬ(&Дата) МЕЖДУ МЕСЯЦ(&Начало) * 100 + ДЕНЬ(&Начало) И МЕСЯЦ(&Конец) * 100 + ДЕНЬ(&Конец)
        ИНАЧЕ МЕСЯЦ(&Дата) * 100 + ДЕНЬ(&Дата) >= МЕСЯЦ(&Начало) * 100 + ДЕНЬ(&Начало) 
              ИЛИ МЕСЯЦ(&Дата) * 100 + ДЕНЬ(&Дата) <= МЕСЯЦ(&Конец) * 100 + ДЕНЬ(&Конец)
    КОНЕЦ КАК ВходитВИнтервал

Метод 3: Универсальная логическая формула

Можно ли обойтись без условий Если...Тогда? Да, если применить принципы булевой алгебры. Проанализируем логику: если начало меньше конца, нам нужно одновременное выполнение условий (И). Если начало больше конца (переход через год), нам нужно выполнение хотя бы одного из условий (ИЛИ).

Выясним, как написать это максимально лаконично. Пусть Т — проверяемая точка, Н — начало, К — конец. Все переменные приведены к числовому виду (ММДД).

Универсальное условие:
(Н <= К И Т >= Н И Т <= К) ИЛИ (Н > К И (Т >= Н ИЛИ Т <= К))

Такой подход считается наиболее "чистым" с точки зрения математики, так как он исключает ветвление алгоритма и может быть записан одной строкой в любом выражении.

Метод 4: Использование нормализованного смещения (Номер дня в году)

Еще один продвинутый способ — перевод дат в порядковый номер дня в году (от 1 до 366). Рассмотрим ситуацию, когда нам нужно проверить интервал "1 октября - 1 марта". Мы можем вычислить "расстояние" от начала интервала до конца и от начала интервала до нашей даты.

Посмотрим на алгоритм "циклического сдвига":

  1. Вычисляем номер дня года для всех трех дат (используя високосный год).
  2. Нормализуем конец интервала относительно начала. Если ДеньКонца < ДеньНачала, прибавляем 366.
  3. Нормализуем проверяемую дату. Если ДеньПров < ДеньНачала, прибавляем 366.
  4. Теперь просто сравниваем: НормДата <= НормКонец.

Пример кода на 1С:


// Используем вспомогательную дату для получения номера дня в году
Функция НомерДня(ДатаСобытия)
    Возврат ДеньГода(Дата(2000, Месяц(ДатаСобытия), День(ДатаСобытия)));
КонецФункции

// Основная проверка
ДеньНач = НомерДня(ДатаНачала);
ДеньКон = НомерДня(ДатаКонца);
ДеньПров = НомерДня(НашаДата);

ДлинаИнтервала = ?(ДеньКон >= ДеньНач, ДеньКон - ДеньНач, 366 + (ДеньКон - ДеньНач));
СмещениеДаты = ?(ДеньПров >= ДеньНач, ДеньПров - ДеньНач, 366 + (ДеньПров - ДеньНач));

Результат = (СмещениеДаты <= ДлинаИнтервала);

Важные замечания по 29 февраля

При работе с днями рождения или ежегодными событиями всегда стоит помнить о 29 февраля. Разберем основные правила:

Итоги

Мы рассмотрели четыре способа решения задачи. Для обычного прикладного кода мы рекомендуем Метод 1 (приведение к 2000 году) из-за его читаемости. Для сложных запросов и высокой производительности лучше использовать Метод 2 (число ММДД). А если вы стремитесь к максимальной математической элегантности, ваш выбор — Метод 3.

← На главную