В разработке на платформе «1С:Предприятие 8» (особенно в конфигурациях «1С:Зарплата и управление персоналом» или «1С:ERP») часто возникает задача рассчитать точный стаж сотрудника или интервал между двумя событиями. Нам нужно получить результат не в безликих секундах или днях, а в интуитивно понятном для кадровика и пользователя виде: «X лет, Y месяцев, Z дней».
Давайте подробно разберем, как устроена работа с датами в 1С, проанализируем популярные ошибки и рассмотрим лучшие способы решения этой задачи — как программные, так и с использованием встроенных механизмов конфигурации.
Прежде всего выясним причину, почему обычное арифметическое действие «минус» не дает нужного представления. В платформе «1С:Предприятие» тип данных Дата хранит в себе как саму календарную дату, так и время с точностью до секунды.
Когда мы вычитаем одну дату из другой, платформа возвращает разницу между ними в секундах (тип данных Число). Например, результатом вычисления выражения:
РазностьСекунд = Дата(2023, 10, 15) - Дата(2023, 10, 10);
Будет число 432 000. Это ровно 5 суток, выраженных в секундах (5 дней * 24 часа * 60 минут * 60 секунд). Просто так преобразовать это число в «дату» или готовую строку стажа не получится, так как календарные месяцы имеют разную длительность (28, 29, 30 или 31 день), а годы могут быть високосными.
Иногда разработчики пытаются применить хитрый математический трюк. Мы берем полученную разность в секундах, прибавляем её к «пустой» дате начала нашей эры 0001-01-01 и вычленяем из результата год, месяц и день, вычитая единицу. Пример такого кода:
РазницаСекунд = Дата(2020, 2, 16) - Дата(2019, 1, 15);
РазницаДат = Дата(1, 1, 1, 0, 0, 0) + РазницаСекунд;
// Попытка получить составляющие:
Дней = День(РазницаДат) - 1;
Месяцев = Месяц(РазницаДат) - 1;
Лет = Год(РазницаДат) - 1;
Почему этот метод использовать категорически нельзя? Он дает ложные результаты из-за календарной структуры.
Когда вы прибавляете секунды к дате 0001-01-01, платформа распределяет их по сетке первого года нашей эры. Однако первый год нашей эры не был високосным (февраль длился 28 дней). Если реальный период стажа сотрудника проходил через високосный год (например, 2020 или 2024), то прибавление этого интервала к началу эры приведет к потере 1 дня стажа.
В примере выше реальный стаж между 15.01.2019 и 16.02.2020 составляет 1 год, 1 месяц и 1 день. Но из-за високосного 2020 года физическое число дней равно 397. Если прибавить 397 дней к 0001-01-01, мы попадем на дату 0002-02-01. Вычитая единицы, мы получим: 1 год, 1 месяц и 0 дней. Один день стажа сотрудника был утерян из-за календарной погрешности.
Если мы работаем в конфигурации «1С:Зарплата и управление персоналом 3.1» (или любой другой современной конфигурации, где есть подсистема учета кадров), нам не нужно изобретать велосипед. В БСП и кадровом модуле уже предусмотрены надежные функции, которые учитывают все високосные годы и особенности месяцев.
Основным инструментом является функция РазобратьРазностьДат, расположенная в общем модуле ОбщегоНазначенияЗК (ЗК — Зарплата и Кадры). Рассмотрим пример её вызова:
ДатаНачала = Дата(2019, 1, 15);
ДатаОкончания = Дата(2020, 2, 16);
Лет = 0;
Месяцев = 0;
Дней = 0;
ОбщегоНазначенияЗК.РазобратьРазностьДат(ДатаОкончания, ДатаНачала, Лет, Месяцев, Дней);
// На выходе в переменных Лет, Месяцев, Дней мы получим точные данные:
// Лет = 1, Месяцев = 1, Дней = 1.
Этот метод полностью безопасен, так как разработан методистами фирмы «1С» специально для расчета стажа сотрудников в соответствии с законодательством.
Если нам нужно не просто получить числа, а сразу вывести красивую строку вида «1 год 1 месяц 1 день», мы можем воспользоваться встроенной функцией Библиотеки Стандартных Подсистем (БСП) из общего модуля ОбщегоНазначения:
ДатаНачала = Дата(2019, 1, 15);
ДатаОкончания = Дата(2020, 2, 16);
ПредставлениеИнтервала = ОбщегоНазначения.ИнтервалВремениСтрокой(ДатаНачала, ДатаОкончания);
// Функция вернет готовую строку с правильными окончаниями слов
Обратите внимание, что представление может немного отличаться в зависимости от версии БСП, поэтому для строгих кадровых печатных форм надежнее получить числа через РазобратьРазностьДат и самостоятельно собрать строку с помощью функции ФорматСубконто или собственных алгоритмов склонения слов.
Что делать, если мы пишем отчет на Системе компоновки данных (СКД) или разрабатываем сложный запрос, где нельзя напрямую обратиться к процедурам общих модулей? Мы можем реализовать расчет прямо в тексте запроса или в вычисляемых полях СКД.
Для этого используется функция РАЗНОСТЬДАТ. Чтобы рассчитать разность корректно, мы должны использовать следующую логику:
РАЗНОСТЬДАТ(ДатаПриема, КонецПериода, "МЕСЯЦ").Посмотрим на пример формулы для вычисляемого поля СКД для точного расчета полных месяцев:
РазностьДат(ДатаПриема, КонецПериода, "МЕСЯЦ")
+ Выбор
Когда День(ДатаПриема) > День(КонецПериода) Тогда -1
Иначе 0
Конец
Зная точное количество полных месяцев, мы можем легко вычислить количество дней, прибавив эти месяцы к дате начала с помощью функции ДобавитьКДате и сравнив результат с конечной датой.
Если перед вами стоит задача посчитать разницу не в годах и месяцах (где важна календарная сетка), а, например, просто перевести разницу в секундах в дни, часы и минуты (например, для оценки времени работы сеанса или выполнения регламентного задания), можно использовать классический алгоритм деления остатка. Рассмотрим этот пример кода:
Функция РазбитьСекунды(ВсегоСекунд) Экспорт
Секунды = ВсегоСекунд % 60;
ВсегоМинут = (ВсегоСекунд - Секунды) / 60;
Минуты = ВсегоМинут % 60;
ВсегоЧасов = (ВсегоМинут - Минуты) / 60;
Часы = ВсегоЧасов % 24;
Дни = (ВсегоЧасов - Часы) / 24;
ПредставлениеДней = ?(Дни > 0, Строка(Дни) + " дн. ", "");
Результат = СтрШаблон("%1%2 ч. %3 мин. %4 сек.",
ПредставлениеДней,
Формат(Часы, "ЧЦ=2; ЧДЦ=0; ЧГ=0"),
Формат(Минуты, "ЧЦ=2; ЧДЦ=0; ЧГ=0"),
Формат(Секунды, "ЧЦ=2; ЧДЦ=0; ЧГ=0")
);
Возврат Результат;
КонецФункции
Этот ручной алгоритм идеально подходит для технических задач замера времени, но помните, что для расчета трудового стажа сотрудников его использовать нельзя — для кадровых задач всегда выбирайте Способ 1 или Способ 3.