Как получить все даты понедельников в заданном диапазоне в 1С?

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

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

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

Метод 1: Простой перебор дат с ежедневной проверкой

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

Принцип работы

  1. Определение диапазона: Мы задаем ДатаНачала и ДатаОкончания периода, в котором будем искать дни недели.
  2. Инициализация: Создаем новый массив, который будет хранить найденные даты, и устанавливаем текущую дату для итерации, обычно равную НачалоДня(ДатаНачала). Использование НачалоДня() является важной практикой, чтобы избежать неточностей из-за временной компоненты даты.
  3. Цикл перебора: Запускаем цикл, который будет продолжаться до тех пор, пока ТекущаяДата не превысит ДатаОкончания.
  4. Проверка дня недели: На каждой итерации цикла используем встроенную функцию ДеньНедели() (для 1С 8.х) или НомерДняНедели() (для 1С 7.7) для определения дня недели ТекущейДаты. В 1С ДеньНедели() возвращает число от 1 (понедельник) до 7 (воскресенье).
  5. Добавление в результат: Если ДеньНедели(ТекущаяДата) совпадает с искомым значением (например, 1 для понедельника), мы добавляем ТекущуюДату в наш результирующий массив.
  6. Переход к следующему дню: В конце каждой итерации мы прибавляем один день к ТекущейДате. В 1С это можно сделать, прибавив количество секунд в сутках: ТекущаяДата = ТекущаяДата + 60 * 60 * 24;. Система 1С корректно обрабатывает такие операции, учитывая високосные годы и переходы на летнее/зимнее время, автоматически нормализуя дату к следующему календарному дню.

Пример реализации для 1С 8.х

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


// Функция ПолучитьДатыДняНедели
// Параметры:
//   ИскомыйДеньНедели - Число (1=Понедельник, 7=Воскресенье)
//   ДатаНачала      - Дата (начало периода)
//   ДатаОкончания   - Дата (конец периода)
// Возвращает: Массив дат, соответствующих ИскомомуДнюНедели
Функция ПолучитьДатыДняНедели(ИскомыйДеньНедели, ДатаНачала, ДатаОкончания) Экспорт
    Результат = Новый Массив;
    
    // Нормализуем начальную дату до начала дня для корректных сравнений и расчетов.
    ТекущаяДата = НачалоДня(ДатаНачала); 

    // Цикл по всем дням в заданном диапазоне
    Пока ТекущаяДата <= ДатаОкончания Цикл
        // Проверяем, является ли текущая дата искомым днем недели
        Если ДеньНедели(ТекущаяДата) = ИскомыйДеньНедели Тогда
            Результат.Добавить(ТекущаяДата);
        КонецЕсли;
        
        // Переходим к следующему дню, прибавляя 24 часа в секундах.
        // 1С автоматически обрабатывает переходы на летнее/зимнее время и високосные годы.
        ТекущаяДата = ТекущаяДата + 60 * 60 * 24; 
    КонецЦикла;
    
    Возврат Результат;
КонецФункции

// Пример использования функции для получения понедельников с 18.05.2022 по 31.05.2022
ДатаНачалаПериода = Дата("20220518");
ДатаОкончанияПериода = Дата("20220531");
ИскомыйДень = 1; // 1 соответствует понедельнику в 1С

МассивПонедельников = ПолучитьДатыДняНедели(ИскомыйДень, ДатаНачалаПериода, ДатаОкончанияПериода);

// Для вывода результата в консоль:
// Для Каждого ПонедельникИзМассива Из МассивПонедельников Цикл
//     Сообщить("Найден понедельник: " + ПонедельникИзМассива);
// КонецЦикла;
// Результат для данного примера будет: 23.05.2022, 30.05.2022

Пример реализации для 1С 7.7

В 1С 7.7 используется похожий синтаксис для работы с датами и циклами, но для определения дня недели применяется функция НомерДняНедели().


// Пример для 1С 7.7
ДатаНачала = Дата("18.05.2022");
ДатаОкончания = Дата("31.05.2022");

Для Счетчик = ДатаНачала По ДатаОкончания Цикл
    // НомерДняНедели(Счетчик) = 1 также соответствует понедельнику
    Если НомерДняНедели(Счетчик) = 1 Тогда;
        Сообщить("Понедельник - "+Счетчик);
    КонецЕсли;
КонецЦикла;

Этот метод, хотя и прост, имеет потенциальный недостаток в производительности: если диапазон дат очень большой (например, несколько лет), функция ДеньНедели() будет вызываться для каждого дня, что может замедлить выполнение кода.

Метод 2: Оптимизированный перебор с шагом в 7 дней

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

Принцип работы

  1. Определение диапазона: Как и в первом методе, задаем ДатаНачала и ДатаОкончания.
  2. Поиск первого искомого дня: Самый важный шаг — это найти первую дату в диапазоне, которая является нужным нам днем недели (например, понедельником). Мы берем ДатаНачала и, если она не является искомым днем недели, прибавляем к ней необходимое количество дней, чтобы получить ближайший следующий искомый день недели.
  3. Цикл с шагом 7 дней: Начиная с найденного первого искомого дня, мы циклически прибавляем к нему 7 дней (что эквивалентно 7 * 24 * 3600 секунд) и добавляем каждую полученную дату в результирующий массив, пока не превысим ДатаОкончания.

Алгоритм поиска первого понедельника

Для того чтобы найти первый понедельник, который находится в диапазоне [ДатаНачала, ДатаОкончания], или сразу после ДатаНачала, мы можем использовать следующую логику:

  1. Берем НачалоДня(ДатаНачала) для нормализации.
  2. Определяем день недели этой нормализованной даты с помощью ДеньНедели().
  3. Если НачалоДня(ДатаНачала) уже является понедельником (ДеньНедели() возвращает 1), то это и есть наш первый понедельник.
  4. В противном случае, нам нужно прибавить к НачалоДня(ДатаНачала) столько дней, чтобы попасть на следующий понедельник. Формула для этого: (8 - ДеньНедели(НачалоДня(ДатаНачала))) * 24 * 3600 секунд.
    • Например, если НачалоДня(ДатаНачала) — среда (ДеньНедели() = 3), то 8 - 3 = 5. Прибавив 5 дней к среде, мы получим следующий понедельник.
    • Если НачалоДня(ДатаНачала) — воскресенье (ДеньНедели() = 7), то 8 - 7 = 1. Прибавив 1 день к воскресенью, мы получим понедельник.

Пример реализации для 1С 8.х (функция МассивПонедельников)

Эта функция демонстрирует оптимизированный подход, находя первый понедельник и затем используя шаг в 7 дней.


// Функция МассивПонедельников
// Параметры:
//   ДатаНачала    - Дата (начало периода)
//   ДатаОкончания - Дата (конец периода)
// Возвращает: Массив дат, являющихся понедельниками в заданном диапазоне
Функция МассивПонедельников(ДатаНачала, ДатаОкончания) Экспорт
    Понедельники = Новый Массив;
    
    // Нормализуем начальную дату до начала дня
    НачальнаяДатаНормализована = НачалоДня(ДатаНачала);
    
    // Определяем день недели нормализованной начальной даты
    // В 1С: 1 = Понедельник, 7 = Воскресенье
    НачальныйДеньНедели = ДеньНедели(НачальнаяДатаНормализована); 

    // Вычисляем дату первого понедельника, который >= НачальнаяДатаНормализована.
    // Если НачальнаяДатаНормализована уже понедельник, берем ее.
    // Иначе, вычисляем, сколько дней нужно прибавить, чтобы дойти до следующего понедельника.
    ТекущийПонедельник = НачальнаяДатаНормализована;
    Если НачальныйДеньНедели <> 1 Тогда
        // Количество дней до следующего понедельника: (8 - текущий_день_недели).
        // Например, если вторник (2), то (8-2)=6 дней до понедельника.
        // Если воскресенье (7), то (8-7)=1 день до понедельника.
        ТекущийПонедельник = ТекущийПонедельник + (8 - НачальныйДеньНедели) * 24 * 3600;
    КонецЕсли;

    // После расчета убедимся, что ТекущийПонедельник не раньше ДатаНачала.
    // Этот шаг важен, если исходная ДатаНачала имела время, а мы работали с НачалоДня(ДатаНачала).
    // Хотя в данном случае, с учетом НачалоДня(ДатаНачала), ТекущийПонедельник всегда будет >= НачалоДня(ДатаНачала).
    // Если бы мы не нормализовали, этот шаг мог бы быть критичен.
    // Например, если ДатаНачала была "18.05.2022 10:00:00" (среда) и мы искали понедельник 23.05.2022,
    // а потом ДатаОкончания "20.05.2022".
    // Важно, чтобы найденный ТекущийПонедельник был в пределах [ДатаНачала, ДатаОкончания].
    // Так как мы уже нормализовали ДатаНачала, достаточно просто убедиться, что
    // ТекущийПонедельник находится в заданном диапазоне.

    // Цикл по понедельникам с шагом в 7 дней
    Пока ТекущийПонедельник <= ДатаОкончания Цикл
        // Добавляем понедельник в массив только если он находится в исходном диапазоне.
        // Это условие `ТекущийПонедельник >= НачальнаяДатаНормализована` избыточно
        // после правильного определения первого понедельника, но не повредит.
        Если ТекущийПонедельник >= НачальнаяДатаНормализована Тогда 
            Понедельники.Добавить(ТекущийПонедельник);
        КонецЕсли;
        
        // Переходим к следующему понедельнику, прибавляя ровно 7 дней (в секундах).
        // 1С автоматически обрабатывает особенности календаря.
        ТекущийПонедельник = ТекущийПонедельник + 7 * 24 * 3600; 
    КонецЦикла;
    
    Возврат Понедельники;
КонецФункции

// Пример использования функции для получения понедельников с 18.05.2022 по 31.05.2022
ДатаНачалаПериода = Дата("20220518");
ДатаОкончанияПериода = Дата("20220531");

МассивПонедельников = МассивПонедельников(ДатаНачалаПериода, ДатаОкончанияПериода);

// Для вывода результата в консоль:
// Для Каждого ПонедельникИзМассива Из МассивПонедельников Цикл
//     Сообщить("Найден понедельник (оптимизированно): " + ПонедельникИзМассива);
// КонецЦикла;
// Результат для данного примера будет: 23.05.2022, 30.05.2022

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

Важные нюансы при работе с датами в 1С

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

  1. Функция НачалоДня(): Мы настоятельно рекомендуем всегда использовать функцию НачалоДня() для нормализации дат перед их использованием в логике сравнений или арифметических операций. Если в дате присутствует временная компонента (часы, минуты, секунды), это может привести к неожиданным результатам. Например, дата "01.01.2023 10:00:00" при сравнении с "01.01.2023 09:00:00" будет больше, а прибавив к ней 24 часа, мы можем получить "02.01.2023 10:00:00", что не всегда является "началом следующего дня" с точки зрения календарной логики. Нормализация до начала дня (НачалоДня(Дата)) гарантирует, что мы работаем исключительно с календарными датами без учета времени.
  2. Влияние високосных годов и переходов на летнее/зимнее время (DST): В 1С, прибавляя или отнимая фиксированное количество секунд для перехода между днями или неделями, платформа автоматически и корректно обрабатывает особенности календарного времени. Это означает:
    • Високосные годы: При прибавлении 24 * 3600 секунд к дате, которая предшествует 29 февраля високосного года, система правильно перейдет на 29 февраля, а затем на 1 марта. День недели при этом сохранится в соответствии с календарной логикой.
    • Переход на летнее/зимнее время: В дни, когда происходит перевод часов (весной на летнее время, осенью на зимнее), сутки могут содержать 23 или 25 часов. Несмотря на это, прибавление 60 * 60 * 24 секунд к дате приведет к переходу ровно на следующий календарный день, а не к сдвигу времени суток. То есть, если мы прибавили 24 часа к "01.01.2023 00:00:00", мы всегда получим "02.01.2023 00:00:00", независимо от того, был ли в этот интервал переход на DST. Это делает операции с датами через секунды в 1С надежными для определения календарных дней.

    Таким образом, опасения относительно високосных годов или DST при использовании фиксированных секунд для перехода на следующий день или неделю в 1С обычно беспочвенны, так как платформа берет эти аспекты на себя.

  3. Производительность: Мы уже обсудили, что для больших диапазонов дат, метод с шагом в 7 дней (Метод 2) значительно превосходит по производительности метод ежедневного перебора (Метод 1). Если ваш период анализа может охватывать месяцы или годы, всегда отдавайте предпочтение оптимизированному подходу, чтобы избежать замедления работы системы.

Заключение

Мы подробно рассмотрели два основных подхода к решению задачи получения списка дат конкретного дня недели в заданном диапазоне в 1С. Метод прямого перебора (Метод 1) прост в реализации и понимании, подходит для небольших временных интервалов и ситуаций, где производительность не является критически важной. Однако для повышения эффективности, особенно при работе с длительными периодами, мы настоятельно рекомендуем использовать оптимизированный метод (Метод 2), который сначала находит первый искомый день недели, а затем циклически прибавляет 7 дней.

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

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

← На главную