Работа с датами и временем — одна из самых коварных тем в программировании. В платформе «1С:Предприятие» тип данных Дата используется ежедневно, однако большинство разработчиков сталкиваются только с современным периодом. Но как только дело доходит до исторических документов, архивных данных или сложных календарных расчетов, платформа начинает вести себя неожиданным образом.
В этой статье мы подробно разберем, как 1С работает с датами на стыке эпох и календарей, почему попытка вычесть один день внутри конструктора даты приводит к аварийному завершению программы, и как платформа взаимодействует с СУБД при записи «древних» дат.
Рассмотрим распространенную ошибку, с которой сталкиваются разработчики при попытке получить дату предыдущего дня динамически. Посмотрим на следующий код, который вызывает ошибку выполнения:
// Попытка получить 30 ноября 1905 года путем вычитания единицы из дня
НеправильнаяДата = Дата(1905, 12, 1 - 1);
Проанализируем ситуацию и выясним причину сбоя. Конструктор Дата(Год, Месяц, День) ожидает на входе три целочисленных параметра. Перед тем как передать аргументы в конструктор, платформа вычисляет выражение во всех параметрах. В данном случае выражение 1 - 1 в третьем параметре дает результат 0.
В итоге платформа пытается выполнить инструкцию Дата(1905, 12, 0). Поскольку нулевого дня в календарном месяце не существует (минимальное значение для дня всегда равно 1), система выдает ошибку времени выполнения «Недопустимое значение параметра». Платформа 1С не умеет автоматически сдвигать месяц назад, если в параметр дня передан ноль или отрицательное число.
Для правильного вычисления дат необходимо выполнять арифметические операции над уже созданным объектом типа Дата. В 1С значения этого типа хранятся в секундах. Соответственно, чтобы вычесть или прибавить один день, нужно вычитать или прибавлять количество секунд в сутках — 86 400 (24 часа * 3600 секунд).
Рассмотрим правильный вариант написания этого кода:
// Шаг 1: Создаем базовую дату (1 декабря 1905 года)
БазоваяДата = Дата(1905, 12, 1);
// Шаг 2: Вычитаем 86400 секунд (1 сутки)
ПредыдущийДень = БазоваяДата - 86400;
// В результате переменная ПредыдущийДень будет содержать: 30.11.1905 00:00:00
Если вам требуется изменить дату на один или несколько месяцев, используйте встроенную функцию платформы ДобавитьМесяц(), которая корректно обрабатывает разное количество дней в месяцах и високосные годы:
// Сдвиг даты на 1 месяц назад
ПрошлыйМесяц = ДобавитьМесяц(БазоваяДата, -1);
Исторически переход с юлианского календаря («старого стиля») на григорианский («новый стиль») произошел в октябре 1582 года по указу Папы Римского Григория XIII. Чтобы компенсировать накопившуюся погрешность, было решено после четверга 4 октября сразу перейти к пятнице 15 октября. Таким образом, дни с 5 по 14 октября 1582 года в григорианском календаре официально перестали существовать.
Платформа 1С использует так называемый пролептический григорианский календарь. Однако при обработке дат в районе проведения реформы 1582 года платформа ведет себя очень специфически. Разберем этот пример:
// Попробуем инициализировать "несуществующую" дату 5 октября 1582 года
ДатаРеформы = Дата(1582, 10, 5);
Если мы выполним этот код и проверим результат, то увидим удивительные вещи:
День(ДатаРеформы) вернет число 5.Формат(ДатаРеформы, "ДЛФ=DD") мы получим строковое представление "15 октября 1582 г.". Платформа автоматически сдвигает представление даты на первый день нового стиля.
СледующийДень = ДатаРеформы + 24 * 3600;
В результате мы получим 16.10.1582 00:00:00.
Важный технический нюанс: Дата 15 октября 1582 года (первый день григорианского календаря, известный в астрономии как начало лилианского периода) является базовой точкой отсчета для генерации уникальных идентификаторов UUID (GUID) версии 1. По стандарту RFC 4122, системное время в таких UUID измеряется в 100-наносекундных интервалах именно с этой даты. Разработчики платформы 1С учитывают эту особенность при генерации уникальных идентификаторов ссылочных объектов базы данных.
В Российской империи 1900 год действительно содержал дату 29 февраля, так как страна продолжала жить по юлианскому календарю (переход на новый стиль состоялся только в феврале 1918 года по декрету Совнаркома). По юлианским правилам високосным является каждый четвертый год без исключений.
Однако по правилам григорианского календаря вековые годы (оканчивающиеся на "00") признаются високосными только в том случае, если они делятся без остатка на 400. Число 1900 на 400 нацело не делится, следовательно, в григорианском стиле 1900 год был простым (365 дней), и даты 29 февраля в нем никогда не существовало.
Поскольку встроенный календарь 1С базируется на григорианских правилах, попытка создать эту дату в коде приведет к ошибке:
// Вызовет ошибку выполнения в 1С:
НесуществующаяДата = Дата(1900, 2, 29);
При этом многие разработчики удивляются, почему аналогичная дата без проблем обрабатывается в табличном редакторе Microsoft Excel. Ответ кроется в историческом компромиссе:
Если вы разрабатываете историческую базу данных или учетную систему музейных экспонатов в 1С, вы можете столкнуться с ограничением СУБД MS SQL Server.
Старый тип данных DATETIME в MS SQL Server не поддерживает хранение дат ранее 1 января 1753 года. Это ограничение связано с тем, что Великобритания и её колонии перешли на григорианский календарь только в сентябре 1752 года, пропустив 11 дней. Чтобы избавить разработчиков баз данных от сложнейших вычислений разницы дат за периоды, содержащие этот календарный разрыв, создатели СУБД Sybase (на основе кода которой позже создавался Microsoft SQL Server) ограничили минимальную дату началом первого полного года по новому стилю — 1753-м.
В платформе «1С:Предприятие» пустая (дефолтная) дата представлена как 01.01.0001 00:00:00. Чтобы иметь возможность записывать пустые и сверхранние даты в СУБД без ошибок переполнения, разработчики платформы реализовали механизм под названием «Смещение дат».
При создании базы данных в настройках можно указать смещение дат (обычно равное 2000 годам). Работает это следующим образом:
01.01.0001 физически хранится в таблицах SQL как 01.01.2001.01.01.0001.Если ваша база данных развернута на современных СУБД с поддержкой типа данных DATETIME2 (который поддерживает даты начиная с 1 года н.э.), смещение дат можно не использовать, однако для совместимости старых конфигураций этот механизм до сих пор активно применяется.
Подведем итог и сформулируем ключевые правила для разработчиков 1С:
Дата(Г, М, Д) требует точных календарных значений. Для смещения дат используйте арифметические операции над результатом (в секундах) или системную функцию ДобавитьМесяц().