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

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

Работа с датами и временем — одна из самых коварных тем в программировании. В платформе «1С:Предприятие» тип данных Дата используется ежедневно, однако большинство разработчиков сталкиваются только с современным периодом. Но как только дело доходит до исторических документов, архивных данных или сложных календарных расчетов, платформа начинает вести себя неожиданным образом.

В этой статье мы подробно разберем, как 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);

2. Переход на григорианский календарь в октябре 1582 года

Исторически переход с юлианского календаря («старого стиля») на григорианский («новый стиль») произошел в октябре 1582 года по указу Папы Римского Григория XIII. Чтобы компенсировать накопившуюся погрешность, было решено после четверга 4 октября сразу перейти к пятнице 15 октября. Таким образом, дни с 5 по 14 октября 1582 года в григорианском календаре официально перестали существовать.

Платформа 1С использует так называемый пролептический григорианский календарь. Однако при обработке дат в районе проведения реформы 1582 года платформа ведет себя очень специфически. Разберем этот пример:


// Попробуем инициализировать "несуществующую" дату 5 октября 1582 года
ДатаРеформы = Дата(1582, 10, 5);

Если мы выполним этот код и проверим результат, то увидим удивительные вещи:

  1. Функция День(ДатаРеформы) вернет число 5.
  2. Однако при попытке форматирования этой даты через Формат(ДатаРеформы, "ДЛФ=DD") мы получим строковое представление "15 октября 1582 г.". Платформа автоматически сдвигает представление даты на первый день нового стиля.
  3. Если мы прибавим к этой дате одни сутки (86 400 секунд): СледующийДень = ДатаРеформы + 24 * 3600; В результате мы получим 16.10.1582 00:00:00.

Важный технический нюанс: Дата 15 октября 1582 года (первый день григорианского календаря, известный в астрономии как начало лилианского периода) является базовой точкой отсчета для генерации уникальных идентификаторов UUID (GUID) версии 1. По стандарту RFC 4122, системное время в таких UUID измеряется в 100-наносекундных интервалах именно с этой даты. Разработчики платформы 1С учитывают эту особенность при генерации уникальных идентификаторов ссылочных объектов базы данных.

3. Существовала ли дата 29 февраля 1900 года? Разница между 1С и Microsoft Excel

В Российской империи 1900 год действительно содержал дату 29 февраля, так как страна продолжала жить по юлианскому календарю (переход на новый стиль состоялся только в феврале 1918 года по декрету Совнаркома). По юлианским правилам високосным является каждый четвертый год без исключений.

Однако по правилам григорианского календаря вековые годы (оканчивающиеся на "00") признаются високосными только в том случае, если они делятся без остатка на 400. Число 1900 на 400 нацело не делится, следовательно, в григорианском стиле 1900 год был простым (365 дней), и даты 29 февраля в нем никогда не существовало.

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


// Вызовет ошибку выполнения в 1С:
НесуществующаяДата = Дата(1900, 2, 29);

При этом многие разработчики удивляются, почему аналогичная дата без проблем обрабатывается в табличном редакторе Microsoft Excel. Ответ кроется в историческом компромиссе:

4. Как СУБД хранят древние даты: ограничение 1753 года и смещение дат в 1С

Если вы разрабатываете историческую базу данных или учетную систему музейных экспонатов в 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 годам). Работает это следующим образом:

Если ваша база данных развернута на современных СУБД с поддержкой типа данных DATETIME2 (который поддерживает даты начиная с 1 года н.э.), смещение дат можно не использовать, однако для совместимости старых конфигураций этот механизм до сих пор активно применяется.

Резюме и правила хорошего тона при работе с датами в 1С

Подведем итог и сформулируем ключевые правила для разработчиков 1С:

  1. Никогда не делайте вычитания или сложения внутри аргументов конструктора дат. Конструктор Дата(Г, М, Д) требует точных календарных значений. Для смещения дат используйте арифметические операции над результатом (в секундах) или системную функцию ДобавитьМесяц().
  2. Помните про особенности 1582 года. При работе с датами эпохи Возрождения учитывайте автоматический сдвиг платформы на григорианский стиль в промежутке с 5 по 14 октября.
  3. Учитывайте разницу с Excel. Если вы пишете обработку выгрузки исторических данных из 1С в Excel, помните о баге совместимости 1900 года в продуктах Microsoft. Даты ранее марта 1900 года могут смещаться при просмотре в табличном редакторе.
  4. Пользуйтесь строковым хранением при отсутствии строгих требований к поиску. Если ваша задача — хранить даты жизни исторических деятелей или даты создания музейных экспонатов, где точность до секунд не важна, надежнее сохранять их в виде обычной строки. Это избавит вас от календарных аномалий разных СУБД.
← На главную