Как выполнить отбор в СКД по часам и минутам для указания конкретных временных промежутков?

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

В рамках работы с системой компоновки данных (СКД) 1С часто возникает необходимость фильтрации данных не только по дате, но и по точному времени. Рассмотрим ситуацию, когда нужно классифицировать записи по временным интервалам, например, для учета приема пищи сотрудников.

Постановка задачи: отбор по временным интервалам

Представим, что сотрудники приходят на прием пищи (завтрак, обед, ужин), отмечаются в 1С, и программа фиксирует точное время отметки. Эти данные пишутся в регистр сведений. Наша задача — определить, к какому приему пищи относится каждая отметка, исходя из заданных временных интервалов. Изначально интервалы заданы по часам, но затем требуется учесть и минуты.

Исходные интервалы:

  1. Завтрак: с 08:00 до 10:00
  2. Обед: с 13:00 до 15:00
  3. Ужин: с 18:00 до 20:00

Обновленные интервалы с учетом минут:

  1. Завтрак: с 08:00 до 10:00 (остается без изменений для данного примера)
  2. Обед: с 13:30 до 15:30
  3. Ужин: с 18:30 до 20:30

Отчет формируется на конкретную дату, и нам нужно корректно классифицировать каждую отметку по времени.

Решение 1: Отбор по часам с использованием функции ЧАС()

Начнем с простого случая, когда отбор требуется выполнить только по часам, без учета минут. Для этого в запросе СКД мы будем использовать функцию ЧАС(), которая извлекает часовую компоненту из поля типа "Дата и время".

Рассмотрим пример запроса, который классифицирует отметки по часам. Мы используем оператор ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ для создания нового вычисляемого поля, которое будет содержать наименование приема пищи.

Пример кода для отбора по часам:


ВЫБРАТЬ
    РегистрСведений.ДатаВремяОтметки,
    ВЫБОР
        КОГДА ЧАС(РегистрСведений.ДатаВремяОтметки) >= 8 И ЧАС(РегистрСведений.ДатаВремяОтметки) < 10 ТОГДА "Завтрак"
        КОГДА ЧАС(РегистрСведений.ДатаВремяОтметки) >= 13 И ЧАС(РегистрСведений.ДатаВремяОтметки) < 15 ТОГДА "Обед"
        КОГДА ЧАС(РегистрСведений.ДатаВремяОтметки) >= 18 И ЧАС(РегистрСведений.ДатаВремяОтметки) < 20 ТОГДА "Ужин"
        ИНАЧЕ "Не вовремя"
    КОНЕЦ КАК ПриемПищи
ИЗ
    РегистрСведений.ОтметкиСотрудников КАК РегистрСведений

В этом примере мы предполагаем, что поле с датой и временем в регистре сведений называется РегистрСведений.ДатаВремяОтметки. Мы последовательно проверяем часовую компоненту на попадание в заданные интервалы. Обратите внимание, что условие ЧАС(...) < N означает, что отметка ровно в N:00 уже не попадает в данный интервал. Если бы интервал был "с 08:00 до 10:00 включительно", то последнему условию < 10 следовало бы заменить на <= 10.

Решение 2: Уточнение отбора до минут с функциями ЧАС() и МИНУТА()

Когда требуется учесть минуты, простого сравнения часов уже недостаточно. Мы должны дополнить условия, используя также функцию МИНУТА(), которая извлекает минутную компоненту из поля типа "Дата и время".

Давайте разберем, как составить комбинированное условие для интервала, например, с 13:30 до 15:30. Нам нужно учесть три сценария для каждой границы интервала:

  1. Время попадает строго между начальным и конечным часом (например, 14:XX, где XX может быть любой минутой).
  2. Время равно начальному часу (13:XX), но минуты должны быть 30 или больше (МИНУТА(...) >= 30).
  3. Время равно конечному часу (15:XX), но минуты должны быть 30 или меньше (МИНУТА(...) <= 30).

Эти сценарии объединяются операторами И и ИЛИ для формирования комплексного условия.

Пример кода для отбора по часам и минутам:


ВЫБРАТЬ
    РегистрСведений.ДатаВремяОтметки,
    ВЫБОР
        // Завтрак: с 08:00 до 10:00 (часы без изменений)
        КОГДА ЧАС(РегистрСведений.ДатаВремяОтметки) >= 8 И ЧАС(РегистрСведений.ДатаВремяОтметки) < 10 ТОГДА "Завтрак"
        
        // Обед: с 13:30 до 15:30
        КОГДА (ЧАС(РегистрСведений.ДатаВремяОтметки) > 13 И ЧАС(РегистрСведений.ДатаВремяОтметки) < 15) // Между 13:59 и 15:00
            ИЛИ (ЧАС(РегистрСведений.ДатаВремяОтметки) = 13 И МИНУТА(РегистрСведений.ДатаВремяОтметки) >= 30) // От 13:30
            ИЛИ (ЧАС(РегистрСведений.ДатаВремяОтметки) = 15 И МИНУТА(РегистрСведений.ДатаВремяОтметки) <= 30) // До 15:30
        ТОГДА "Обед"
        
        // Ужин: с 18:30 до 20:30
        КОГДА (ЧАС(РегистрСведений.ДатаВремяОтметки) > 18 И ЧАС(РегистрСведений.ДатаВремяОтметки) < 20) // Между 18:59 и 20:00
            ИЛИ (ЧАС(РегистрСведений.ДатаВремяОтметки) = 18 И МИНУТА(РегистрСведений.ДатаВремяОтметки) >= 30) // От 18:30
            ИЛИ (ЧАС(РегистрСведений.ДатаВремяОтметки) = 20 И МИНУТА(РегистрСведений.ДатаВремяОтметки) <= 30) // До 20:30
        ТОГДА "Ужин"
        ИНАЧЕ "Не вовремя"
    КОНЕЦ КАК ПриемПищи
ИЗ
    РегистрСведений.ОтметкиСотрудников КАК РегистрСведений

Этот подход является более точным и наглядным по сравнению с некоторыми другими методами, так как мы напрямую оперируем часами и минутами, что делает код более читаемым и понятным. Однако, если границы интервалов станут еще более сложными (например, с учетом секунд), или количество интервалов очень велико, это условие может стать громоздким.

Решение 3: Отбор через сравнение секунд от начала дня

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

Для этого мы используем функцию РАЗНОСТЬДАТ(). Сначала мы определяем начало дня для нашей даты с помощью НАЧАЛОПЕРИОДА(РегистрСведений.ДатаВремяОтметки, ДЕНЬ), затем вычисляем разницу в секундах между началом дня и текущим временем отметки. Полученное значение мы сравниваем с границами интервалов, также выраженными в секундах.

Шаги:

  1. Вычисляем количество секунд для начальной границы интервала. Например, для 13:30 это 13 * 3600 (секунд в часе) + 30 * 60 (секунд в минуте) = 48600 секунд.
  2. Вычисляем количество секунд для конечной границы интервала. Например, для 15:30 это 15 * 3600 + 30 * 60 = 55800 секунд.
  3. Сравниваем полученное количество секунд из поля отметки с этими числовыми границами, используя оператор МЕЖДУ.

Пример кода для отбора по секундам от начала дня:


ВЫБРАТЬ
    РегистрСведений.ДатаВремяОтметки,
    ВЫБОР
        // Завтрак: с 08:00 (8*3600=28800) до 10:00 (10*3600=36000), т.е. до 09:59:59
        КОГДА РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(РегистрСведений.ДатаВремяОтметки, ДЕНЬ), РегистрСведений.ДатаВремяОтметки, СЕКУНДА) МЕЖДУ (8 * 3600) И (10 * 3600 - 1)
        ТОГДА "Завтрак"
        
        // Обед: с 13:30 (13*3600+30*60=48600) до 15:30 (15*3600+30*60=55800), т.е. до 15:29:59
        КОГДА РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(РегистрСведений.ДатаВремяОтметки, ДЕНЬ), РегистрСведений.ДатаВремяОтметки, СЕКУНДА) МЕЖДУ (13 * 3600 + 30 * 60) И (15 * 3600 + 30 * 60 - 1)
        ТОГДА "Обед"
        
        // Ужин: с 18:30 (18*3600+30*60=66600) до 20:30 (20*3600+30*60=73800), т.е. до 20:29:59
        КОГДА РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(РегистрСведений.ДатаВремяОтметки, ДЕНЬ), РегистрСведений.ДатаВремяОтметки, СЕКУНДА) МЕЖДУ (18 * 3600 + 30 * 60) И (20 * 3600 + 30 * 60 - 1)
        ТОГДА "Ужин"
        ИНАЧЕ "Не вовремя"
    КОНЕЦ КАК ПриемПищи
ИЗ
    РегистрСведений.ОтметкиСотрудников КАК РегистрСведений

Обратите внимание на - 1 в конечной границе интервала. Это необходимо, если мы хотим, чтобы конечная точка интервала (например, ровно 10:00:00 или 15:30:00) не попадала в текущий интервал, а скорее до него. Если интервал строго "до 10:00", то (10 * 3600 - 1) означает до 09:59:59. Если условие "до 10:00 включительно", то (10 * 3600). В нашем случае "с ... до ..." обычно подразумевает, что конечная точка не включена, поэтому -1 является корректным. Однако, всегда уточняйте требование к включению границ интервалов.

Решение 4: Сравнение временных частей через "нулевую" дату

Этот метод является одним из самых строгих и часто используемых при необходимости сравнения только временных частей даты, игнорируя день, месяц и год. Мы "отрезаем" дату от времени, приводя его к началу эпохи (01.01.0001), что позволяет сравнивать временные интервалы напрямую как объекты типа "Дата".

Для этого мы сначала получаем количество секунд от начала дня (как в предыдущем методе с использованием РАЗНОСТЬДАТ()), а затем добавляем это количество секунд к дате 01.01.0001 с помощью функции ДОБАВИТЬКДАТЕ(). Границы интервалов также формируем от 01.01.0001 с помощью функции ДАТАВРЕМЯ(Год, Месяц, День, Час, Минута, Секунда).

Пример кода для отбора по "нулевой" дате:


ВЫБРАТЬ
    РегистрСведений.ДатаВремяОтметки,
    ВЫБОР
        // Приведение текущего времени к дате 01.01.0001 для сравнения только временной части
        КОГДА ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(РегистрСведений.ДатаВремяОтметки, ДЕНЬ), РегистрСведений.ДатаВремяОтметки, СЕКУНДА))
            МЕЖДУ ДАТАВРЕМЯ(1, 1, 1, 8, 0, 0) И ДАТАВРЕМЯ(1, 1, 1, 9, 59, 59) // С 08:00:00 до 09:59:59
        ТОГДА "Завтрак"
        
        КОГДА ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(РегистрСведений.ДатаВремяОтметки, ДЕНЬ), РегистрСведений.ДатаВремяОтметки, СЕКУНДА))
            МЕЖДУ ДАТАВРЕМЯ(1, 1, 1, 13, 30, 0) И ДАТАВРЕМЯ(1, 1, 1, 15, 29, 59) // С 13:30:00 до 15:29:59
        ТОГДА "Обед"
        
        КОГДА ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(РегистрСведений.ДатаВремяОтметки, ДЕНЬ), РегистрСведений.ДатаВремяОтметки, СЕКУНДА))
            МЕЖДУ ДАТАВРЕМЯ(1, 1, 1, 18, 30, 0) И ДАТАВРЕМЯ(1, 1, 1, 20, 29, 59) // С 18:30:00 до 20:29:59
        ТОГДА "Ужин"
        ИНАЧЕ "Не вовремя"
    КОНЕЦ КАК ПриемПищи
ИЗ
    РегистрСведений.ОтметкиСотрудников КАК РегистрСведений

Здесь ДАТАВРЕМЯ(1, 1, 1, ЧАС, МИНУТА, СЕКУНДА) создает полную дату, но поскольку мы всегда используем 01.01.0001, фактически сравниваются только временные компоненты. Этот метод считается самым строгим и читаемым для сравнения временных интервалов, поскольку он позволяет оперировать с временными точками как с полноценными датами. Опять же, важно корректно определить границы интервалов, включая или исключая секунды в зависимости от требований (например, "до 10:00:00" это до 09:59:59).

Использование вычисляемых полей в СКД для гибкости

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

Для этого мы переходим в настройки СКД, на вкладку "Вычисляемые поля" и создаем новое поле. В его выражении мы можем использовать те же функции и конструкции, что и в запросе (Час(), Минута(), РазностьДат(), ДобавитьКДате(), ДатаВремя(), ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ ... КОНЕЦ и т.д.).

Преимущества этого подхода:

  1. Упрощение основного запроса: Запрос становится чище и фокусируется только на извлечении исходных данных, без сложной логики классификации.
  2. Централизованное управление логикой: Логика классификации временных интервалов находится в одном месте, что облегчает ее изменение, отладку и поддержку.
  3. Возможность переиспользования: Созданное вычисляемое поле можно легко использовать в различных группировках, условиях отбора, упорядочивания или в других вычисляемых полях отчета.

Мы можем создать вычисляемое поле, например, с именем "ВремяОтметкиБезДаты", используя выражение:


ДОБАВИТЬКДАТЕ(ДАТАВРЕМЯ(1, 1, 1), СЕКУНДА, РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(РегистрСведений.ДатаВремяОтметки, ДЕНЬ), РегистрСведений.ДатаВремяОтметки, СЕКУНДА))

Это поле будет содержать временную часть отметки, приведенную к "нулевой" дате. А затем, мы можем создать второе вычисляемое поле, например, "ПриемПищи", и в его выражении уже использовать более чистую логику сравнения:


ВЫБОР
    КОГДА ВремяОтметкиБезДаты МЕЖДУ ДАТАВРЕМЯ(1, 1, 1, 8, 0, 0) И ДАТАВРЕМЯ(1, 1, 1, 9, 59, 59) ТОГДА "Завтрак"
    КОГДА ВремяОтметкиБезДаты МЕЖДУ ДАТАВРЕМЯ(1, 1, 1, 13, 30, 0) И ДАТАВРЕМЯ(1, 1, 1, 15, 29, 59) ТОГДА "Обед"
    КОГДА ВремяОтметкиБезДаты МЕЖДУ ДАТАВРЕМЯ(1, 1, 1, 18, 30, 0) И ДАТАВРЕМЯ(1, 1, 1, 20, 29, 59) ТОГДА "Ужин"
    ИНАЧЕ "Не вовремя"
КОНЕЦ

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

Заключение

Мы рассмотрели несколько подходов к организации отбора по времени в СКД, начиная от простых сравнений по часам и заканчивая более точными методами с учетом минут и секунд. Выбор конкретного метода зависит от требуемой точности, сложности логики интервалов и предпочтений в читаемости кода. Использование функций ЧАС(), МИНУТА(), РАЗНОСТЬДАТ() и ДАТАВРЕМЯ(), а также возможность выноса сложной логики в вычисляемые поля СКД, предоставляют широкие возможности для решения подобных задач в 1С. Всегда анализируйте свои требования к точности и производительности, чтобы выбрать наиболее подходящий вариант.

← На главную