На практике при разработке в 1С часто возникает задача: пользователь задает период в интерфейсе (например, с 3 февраля по 5 мая), а программе необходимо сформировать таблицу значений или массив, содержащий список месяцев, входящих в этот период (февраль, март, апрель, май). На первый взгляд задача кажется элементарной, однако она таит в себе несколько подводных камней, таких как корректный переход через год, склонение названий месяцев и оптимальное использование ресурсов сервера. Давайте подробно разберем несколько способов решения этой задачи, проанализируем возможные ошибки и выясним причины, по которым архитектурно одни подходы предпочтительнее других.
Рассмотрим базовый алгоритм, который чаще всего первым приходит на ум. Суть подхода заключается в использовании цикла: мы берем начальную дату, определяем начало ее месяца и прибавляем по одному месяцу на каждом шаге цикла, пока не достигнем даты окончания. Сохранять результаты будем в коллекцию, например, в объект Массив или ТаблицаЗначений.
Посмотрим на пример корректно работающего кода:
Функция ПолучитьМесяцыПериода(Знач ДатаНачАрг, Знач ДатаКонАрг)
Результат = Новый Массив;
// Обязательно приводим даты к началу месяца
ДатаНач = НачалоМесяца(ДатаНачАрг);
ДатаКон = НачалоМесяца(ДатаКонАрг);
ДатаТек = ДатаНач;
Пока ДатаТек <= ДатаКон Цикл
// Добавляем текущую дату месяца в массив
Результат.Добавить(ДатаТек);
// Переходим к следующему месяцу
ДатаТек = ДобавитьМесяц(ДатаТек, 1);
КонецЦикла;
Возврат Результат;
КонецФункции
Проанализируем этот код и выделим важные моменты:
НачалоМесяца. Если не привести даты к началу месяца, использование функции ДобавитьМесяц может привести к ошибкам "31-го числа". Если начальная дата — 31 января, добавление одного месяца вернет 28 (или 29) февраля. Следующее добавление месяца к концу февраля даст 28 (или 30) марта, и день "31" будет потерян навсегда. Приведение к 1-му числу месяца решает эту проблему.Знач. Это хорошая практика, чтобы случайно не изменить исходные переменные дат на стороне вызывающего кода.Дата всегда можно продолжить работу: использовать в запросах, сравнениях и форматировании.Предположим, нам необходимо не просто получить даты, а вывести пользователю на экран или в печатную форму строковые названия месяцев. Разберем, как правильно получить название из даты.
В 1С для этого используется функция Формат:
ТекущийМесяцСтрокой = Формат(ТекущаяДата(), "ДФ=MMMM");
Сообщить(ТекущийМесяцСтрокой); // Выведет "Январь", "Февраль" и т.д.
Однако здесь стоит обратить внимание на особенности локализации и падежей:
Формат с параметром ДФ=MMMM всегда возвращает название месяца в именительном падеже (Январь, Февраль). Если для печатной формы требуется родительный падеж (например, "доходы с февраля по май"), стандартный формат не подойдет. В этом случае программисты создают соответствия или массивы со склонениями вручную.
МесяцПоРусски = Формат(ТекущаяДата(), "ДФ=MMMM", "ru_RU");
Рассмотрим ситуацию, когда наша таблица месяцев нужна для передачи в запрос. Например, мы хотим соединить список всех месяцев с регистром накопления "Продажи", чтобы получить отчет, где будут показаны даже те месяцы, в которых продаж не было (вывести нули).
Создавать таблицу на клиенте или сервере приложений, типизировать ее колонки, помещать во временную таблицу через МенеджерВременныхТаблиц, передавать данные между слоями (клиент-сервер-СУБД) — это излишние и ресурсозатратные операции. Куда эффективнее сгенерировать ряды дат прямо в самом запросе на стороне SQL-сервера.
Разберем по шагам, как построить генератор месяцев в запросе. Для этого мы скомпонуем цифры из виртуальных таблиц или констант (декартово произведение) и прибавим их к начальной дате через функцию ДОБАВИТЬКДАТЕ:
ВЫБРАТЬ 0 КАК Цифра ПОМЕСТИТЬ тЦифры
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9;
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&ДатаНач, МЕСЯЦ, Десятки.Цифра * 10 + Единицы.Цифра) КАК МесяцПериода
ПОМЕСТИТЬ ВТ_Месяцы
ИЗ
тЦифры КАК Единицы
ВНУТРЕННЕЕ СОЕДИНЕНИЕ тЦифры КАК Десятки
ПО (Десятки.Цифра * 10 + Единицы.Цифра <= РАЗНОСТЬДАТ(&ДатаНач, &ДатаКон, МЕСЯЦ))
Проанализируем этот запрос. Сначала мы создаем простую временную таблицу тЦифры от 0 до 9. Затем соединяем ее саму с собой, получая числа от 0 до 99 (Десятки * 10 + Единицы). Условие соединения ограничивает генерацию количеством месяцев между датой начала и датой окончания (функция РАЗНОСТЬДАТ). Наконец, ДОБАВИТЬКДАТЕ прибавляет получившееся количество месяцев к стартовой дате. В результате мы мгновенно, без физических таблиц базы данных и нагрузки на сеть, получаем готовую ВТ_Месяцы, которую можно использовать в основном алгоритме отчета. Для аналогичных задач расчета показателей на каждый период также подойдёт способ расчета остатков в запросе без предварительного формирования таблицы дат.
В современных конфигурациях на базе "Библиотеки стандартных подсистем" (БСП), таких как ERP, УТ 11 или Бухгалтерия предприятия 3.0, старайтесь не писать велосипеды. Разработчики фирмы "1С" уже предусмотрели программные интерфейсы для подобных задач.
Посмотрим в сторону общего модуля РаботаСПериодами (или аналогичных модулей в зависимости от версии БСП). В библиотеке присутствуют экспортные функции, способные корректно генерировать периоды, возвращая их в виде массивов или структур. Применение стандартных функций повышает читаемость кода, страхует от логических ошибок перехода через високосные года и делает разработку более стандартизированной.
Выясним причину, по которой нам может вообще не понадобиться писать код алгоритма. Если конечная цель получения месяцев — это вывод отчета (например, анализ продаж), то логичнее всего переложить задачу на плечи Системы Компоновки Данных.
СКД умеет самостоятельно дополнять периоды: если в январе и марте были продажи, а в феврале нет, система сама создаст пустую строку для февраля. Разберем, как это настроить:
После этой настройки СКД автоматически выведет непрерывную шкалу месяцев, избавив вас от необходимости программно генерировать таблицы и писать сложные внешние соединения в запросах.
Мы подробно проанализировали несколько способов получения месяцев между двумя датами. Если вам нужен просто список на форме для выбора пользователем — используйте цикл с НачалоМесяца и коллекцию Массив. Если вы формируете данные для сложной обработки внутри базы — создавайте временные таблицы с датами прямо в SQL-запросе, это сильно оптимизирует работу системы. Для отчетов же лучшим другом программиста остается механизм "Дополнение периодов" платформенной СКД.