YourWorldTime

Проблема 2038 года, объяснённая по существу

· 4 мин чтения

Системы, хранящие время как знаковый 32-битный счётчик секунд, исчерпают запас в январе 2038 года и перескочат в декабрь 1901-го. Почти все современные платформы решили это годами раньше; сегодня уязвимы встраиваемые устройства, унаследованный код и сериализованные данные.

В 03:14:07 UTC 19 января 2038 года число секунд, прошедших с 1 января 1970 года, достигнет 2 147 483 647. Это наибольшее значение, которое вмещает знаковое 32-битное целое. Секундой позже оно обернётся в −2 147 483 648, что такие системы прочитают как 13 декабря 1901 года.

Это настоящая проблема с твёрдо назначенной датой, чем она и необычна среди программных проблем.

Почему 32 бита

Время Unix проектировали в начале 1970-х, когда 32-битное целое было естественным размером машинного слова, а 68 лет запаса казались щедростью. Тип time_t в тех системах был знаковым 32-битным целым, и этот выбор разошёлся по языку C, по каждому языку, обернувшему C, по форматам файлов, сетевым протоколам и схемам баз данных.

Именно знак придаёт переполнению его характерный симптом. Беззнаковый 32-битный счётчик прослужил бы до 2106 года; знаковый делит свой диапазон вокруг 1970-го, поэтому переполнение приземляется в 1901-м, а не в нуле.

Что уже произошло

Проблема 2038 года годами приводит к сбоям в промышленной эксплуатации, потому что программы регулярно вычисляют будущие даты.

  • Расчёты тридцатилетней ипотеки начали пересекать границу в 2008 году.
  • Сроки действия сертификатов, время жизни кэша и запланированные задания натыкались на неё неоднократно.
  • В 2006 году дефект в стандартном тайм-ауте базы данных в AOLserver — заданном в миллиард секунд, что было приемлемо ровно до тех пор, пока не перестало быть — уронил программу, когда вычисленный срок окончания переполнился.

Закономерность в том, что отказ приходит задолго до самой даты, в том коде, который заглядывает дальше всех вперёд.

Что уже исправлено

Положение заметно лучше, чем внушает паника.

  • 64-битные системы: time_t занимает 64 бита практически на любой 64-битной платформе. Запас исчерпается примерно через 292 миллиарда лет.
  • Linux на 32 битах: ядро 5.6 (март 2020 года) ввело 64-битный time_t на 32-битных архитектурах, а крупные дистрибутивы выпустили соответствующее пользовательское окружение.
  • JavaScript: использует число с плавающей точкой двойной точности для миллисекунд, что верно примерно на 275 000 лет в обе стороны от 1970 года. У него этой проблемы никогда не было.
  • Java: Instant хранит 64-битный счётчик секунд плюс наносекунды.
  • Python: целые числа произвольной точности; ограничением служит библиотека C самой платформы.
  • PostgreSQL, MySQL 8.0.28+, SQL Server: 64-битное хранение меток времени.

Где уязвимость остаётся

Встраиваемые системы. Промышленные контроллеры, медицинские приборы, автомобильная электроника, счётчики и датчики — многие рассчитаны на двадцать или тридцать лет службы, многие не имеют пути обновления, многие уже установлены. Здесь сосредоточен основной реальный риск, и централизованного учёта ему нет.

Тип TIMESTAMP в MySQL до версии 8.0.28. Это 32-битный счётчик секунд, и его документированный максимум — 2038-01-19 03:14:07. DATETIME не затронут никогда. Огромный объём рабочих данных лежит именно в уязвимом типе столбца.

Сериализованные данные и форматы файлов. Всё, что однажды записало 32-битное поле времени, остаётся 32-битным полем времени, каким бы новым ни был читатель. Старые архивные форматы, двоичные протоколы и дисковые структуры несут это ограничение дальше.

32-битные сборки, всё ещё в строю. Некоторые маршрутизаторы, приставки и долгоживущие устройства работают с 32-битным пользовательским окружением поверх необновлённого ядра.

Что с этим делать

Проверьте типы хранения. Ищите столбцы int(11) с метками времени, TIMESTAMP в MySQL старых версий и любой двоичный формат с 32-битным полем времени. Именно там данные переживают код.

Испытайте на датах после 2038 года. Задайте тестовой фикстуре 2040 год и прогоните набор тестов. Почти все ошибки 2038 года воспроизводятся тривиально, стоит кому-нибудь попробовать.

Предпочитайте 64-битные целые или строки ISO 8601. У BIGINT миллисекунд или столбца TIMESTAMPTZ нет горизонта, о котором стоит думать. У строки ISO 8601 его нет вовсе — ценой размера и скорости сравнения.

Больше никогда не храните даты ни в чём 32-битном. Экономия на хранении бессмысленна на современном оборудовании, а горизонт теперь лежит внутри срока службы обычной программы.

Прочие временные горизонты

2038 год — ближайший, но не единственный.

  • 2036: 32-битное поле эпохи в NTP перевернётся 7 февраля. NTPv4 с этим справляется, но развёртывания разнятся.
  • 2106: переполнятся беззнаковые 32-битные счётчики секунд.
  • 2262: переполнятся 64-битные счётчики наносекунд — это time.Time в языке Go в наносекундном представлении и datetime64[ns] по умолчанию в pandas.

Последний ближе, чем кажется, для всякого, кто пишет код обработки данных, и это та же самая ошибка в другом масштабе: единица, достаточно мелкая, чтобы быть полезной, в разрядности, казавшейся тогда щедрой.

Как проверить собственные системы

Четыре проверки в порядке возрастания усилий.

Посмотрите типы столбцов базы данных. В PostgreSQL запрос SELECT column_name, data_type FROM information_schema.columns WHERE data_type LIKE '%timestamp%' покажет, что у вас есть; timestamp with time zone — 64-битный и безопасный. В MySQL ищите столбцы TIMESTAMP на серверах до 8.0.28 — уязвимы именно они, а DATETIME не был уязвим никогда.

Переведите часы вперёд. В одноразовом контейнере поставьте системную дату на 2040 год и прогоните набор тестов. Способ грубый и находит удивительно много, потому что почти ни одна ошибка 2038 года не бывает тонкой после пересечения границы.

Поищите 32-битные типы времени. В C и C++ — любой явный int или long, хранящий значение времени на 32-битной цели. В схемах сериализации — любое четырёхбайтовое поле времени. В прикладном коде — каждое место, где метку времени приводят к 32-битному целому для хранения или передачи.

Проверяйте в фикстурах далёкое будущее, а не только ближайшее. Тест, использующий «сейчас плюс сутки», будет проходить до 2038 года, а затем упадёт во вторник. Тест с явной датой в 2040 году падает сегодня, пока есть кому его починить.

Чего делать не стоит

Не «решайте» это переходом на беззнаковые 32-битные целые. Это покупает 68 лет, лишает возможности представить любую дату до 1970 года и перекладывает тот же разговор на того, кто будет сопровождать систему в 2106-м. Разница между 4 и 8 байтами не стоит второй миграции.

Не полагайте, что современный язык вас защитит. Прикладной код может быть совершенно безопасен, тогда как читаемые им данные записало нечто небезопасное — форматы файлов и сетевые протоколы проносят ограничение сквозь любую переписку кода.