При разработке документов планирования, распределения расходов или расчета лимитов в среде «1С:Предприятие» часто возникает задача: распределить общую сумму затрат по дням выбранного месяца. Для этого необходимо знать точное количество дней в этом месяце. Ручной ввод этой величины пользователем замедляет работу и приводит к ошибкам («человеческому фактору»), а использование жестко заданных списков месяцев усложняет код и не учитывает високосные года.
В этой статье мы подробно разберем, как уйти от громоздких перечислений, правильно настроить интерфейс для выбора только месяца и года, автоматически рассчитать количество дней во встроенном языке 1С и исправить популярную ошибку преобразования типов данных.
Начинающие разработчики часто пытаются решить задачу «в лоб»: создают перечисление Перечисление.Месяцы (Январь, Февраль, Март...) и пытаются прописать логику программно:
Если ВыбранныйМесяц = Перечисления.Месяцы.Январь Тогда
ДнейВМесяце = 31;
ИначеЕсли ВыбранныйМесяц = Перечисления.Месяцы.Февраль Тогда
// А как определить, високосный ли год? 28 или 29 дней?
КонецЕсли;
Такой подход имеет три критических недостатка:
Если...ИначеЕсли для каждого месяца.Правильное решение: Использовать обычный реквизит с типом Дата. Платформа 1С содержит мощные встроенные функции для работы с датами, которые берут на себя весь календарный расчет, включая високосные года.
При попытке написать код расчета разработчики иногда сталкиваются с ошибкой:
{Документ.ОперацияКоллективныхДокументов.Форма.ФормаДокумента.Форма(100)}: Преобразование значения к типу Дата не может быть выполнено ПоследняяДата = КонецМесяца(20211231);
Давайте выясним причину возникновения этой ошибки. Она кроется в двух деталях:
20211231 передано как обычное число (тип Число). Функция КонецМесяца() ожидает на вход значение типа Дата. Платформа не может автоматически преобразовать числовую последовательность в дату. Чтобы прописать конкретную дату «жестко» в коде, ее необходимо заключать в одинарные кавычки в формате 'ГГГГММДД'. Правильный синтаксис литерала: '20211231'.Если мы заменим перечисление на реквизит типа Дата, стандартный календарь будет заставлять пользователя выбирать конкретное число (например, 15-е или 20-е число). Для нашей задачи конкретный день не важен — важен сам месяц. Мы можем настроить поле ввода так, чтобы пользователь выбирал и видел только месяц и год.
Рассмотрим, как настроить свойства элемента формы:
ДатаМесяца (тип Дата).ДФ='ММММ гггг' (для англоязычной локализации — DF='MMMM yyyy').ДФ='ММММ гггг' установите в свойстве Формат (Format).Теперь при нажатии на кнопку выбора у пользователя откроется календарь, где можно интерактивно выбрать только месяц и год. Физически в реквизит запишется дата начала выбранного месяца (например, 01.05.2023 00:00:00), но отображаться на форме она будет красиво: «Май 2023 г.».
После того как пользователь выбрал месяц, нам нужно автоматически рассчитать количество дней и заполнить их в табличной части (например, в колонке табличной части Долги с именем ДнейВМесяце).
Для реализации этой логики мы будем использовать событие ПриИзменении поля ввода даты. Ниже приведены рабочие примеры кода для различных типов форм.
В управляемом приложении реквизиты документа находятся внутри объекта-контекста Объект. Обработчик выполняется на клиенте, при этом базовые функции работы с датами доступны непосредственно на клиенте без вызова сервера.
&НаКлиенте
Процедура ДатаМесяцаПриИзменении(Элемент)
// Проверяем, заполнена ли дата пользователем
Если ЗначениеЗаполнено(Объект.ДатаМесяца) Тогда
// Получаем дату последнего дня выбранного месяца
ПоследняяДатаМесяца = КонецМесяца(Объект.ДатаМесяца);
// Функция День() возвращает число месяца (для последнего дня это и есть количество дней в месяце)
КоличествоДней = День(ПоследняяДатаМесяца);
// Обходим строки табличной части и заполняем значение
Для Каждого СтрокаТЧ Из Объект.Долги Цикл
СтрокаТЧ.ДнейВМесяце = КоличествоДней;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Важное примечание: Проверка ЗначениеЗаполнено() строго обязательна. Если пользователь очистит поле даты, а мы попытаемся передать пустую дату в функцию КонецМесяца(), система вернет дату 31.01.0001, и в таблицу запишется число 31, что логически неверно при пустом значении периода.
Если вы работаете в старых конфигурациях (например, УПП, КА 1.1 или старых версиях УТ 10.3), обращение к реквизитам идет напрямую, без префикса Объект.
Процедура ДатаМесяцаПриИзменении(Элемент)
Если ЗначениеЗаполнено(ДатаМесяца) Тогда
ПоследняяДатаМесяца = КонецМесяца(ДатаМесяца);
КоличествоДней = День(ПоследняяДатаМесяца);
Для Каждого СтрокаТЧ Из Долги Цикл
СтрокаТЧ.ДнейВМесяце = КоличествоДней;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Рассмотрим еще два подхода, которые могут пригодиться в специфических задачах.
В 1С разность между двумя датами возвращает количество секунд. Мы можем математически вычислить количество дней, разделив разницу между началом следующего месяца и началом текущего месяца на количество секунд в сутках (86 400 секунд):
НачалоТекущего = НачалоМесяца(Объект.ДатаМесяца);
НачалоСледующего = НачалоМесяца(ДобавитьМесяц(Объект.ДатаМесяца, 1));
КоличествоДней = (НачалоСледующего - НачалоТекущего) / 86400;
Этот метод рабочий, но более громоздкий по сравнению с лаконичной связкой День(КонецМесяца()).
Если вам нужно рассчитать количество дней непосредственно при выборке данных из базы данных (например, в отчетах или при заполнении документов), используйте аналогичную конструкцию внутри языка запросов 1С:
ВЫБРАТЬ
ДЕНЬ(КОНЕЦПЕРИОДА(&ПериодРегистрации, МЕСЯЦ)) КАК КоличествоДнейВМесяце
Мы проанализировали задачу автоматического расчета дней в периоде и выяснили, что оптимальный путь — использовать реквизит с типом Дата с настроенным форматом вывода месяца и года (ДФ='ММММ гггг'). Использование встроенных функций КонецМесяца() и День() гарантирует точный расчет количества дней с учетом специфики любого года, включая високосные периоды, и полностью избавляет от написания сложных алгоритмов проверки дат вручную.