Chrono Mock
ЯзыкРусский

Тестирование проблемы 2038 года в Windows

19 января 2038 года в 03:14:07 UTC заканчивается 32-разрядный знаковый счётчик секунд с 1970 года. Часы Windows идут дальше — а программы, форматы файлов и библиотеки, сохранившие собственную 32-разрядную копию, нет. Вот как найти их в одном приложении, не выставляя на всей машине дату 2038 года.

Где ошибка всё ещё может прятаться

Часы Windows — это 64-разрядный счётчик шагов по 100 наносекунд с 1601 года, так что 2038 года в них нет. Тип time_t в библиотеке времени выполнения C по умолчанию 64-разрядный начиная с Visual Studio 2005. Остаётся код, который выбрал 32-разрядное время или потерял старшую половину по дороге:

  • 32-разрядные сборки, скомпилированные с _USE_32BIT_TIME_T, или напрямую вызывающие _time32 и __time32_t. Документация Microsoft предупреждает, что такое приложение может отказать после 18 января 2038 года.
  • 64-разрядное время, сохранённое в int или long. В Windows оба 32-разрядные, даже в 64-разрядной программе.
  • Библиотеки, форматы файлов, сетевые протоколы и столбцы баз данных, хранящие секунды с 1970 года в 32 битах.
  • Код, прибавляющий годы. Двадцатилетняя лицензия, тридцатилетний график кредита или сертификат, действующий до 2040 года, пересекают границу задолго до наступления 2038 года.

Пересечение границы в одном приложении

Граница задана в UTC, а --at читается в поясе сессии, поэтому при ручной записи момента зафиксируйте пояс как UTC. Готовый сценарий уже несёт абсолютный момент в UTC и в поясе не нуждается.

# Сценарий: сессия стартует прямо на границе, 03:14:07 UTC
chrono run "C:\apps\test.exe" --preset year-2038 --report session.txt

# Стартовать за семнадцать секунд до неё и дать приложению пересечь границу на ходу
chrono run "C:\apps\test.exe" --at 2038-01-19T03:13:50 --zone +00:00 --report session.txt

# Стартовать раньше и идти в 60 раз быстрее — граница наступит примерно через 14 секунд
chrono run "C:\apps\test.exe" --at 2038-01-19T03:00:00 --zone +00:00 --mode x60

# Или идти с сегодняшней датой и перепрыгнуть границу, пока приложение открыто
chrono run "C:\apps\test.exe" --jump-after 5:2038-01-19T03:14:05 --zone +00:00

Через секунду после границы 32-разрядный знаковый счётчик становится равным -2 147 483 648, что читается как 13 декабря 1901 года, 20:45:52 UTC. Дата 1901 года, отрицательное число, элемент, который вдруг просрочился или оказался первым в сортировке, исключение из кода работы с датами — так переполнение выглядит со стороны.

Выставить настоящие часы на 2038 год — другой путь сюда, и он ломает больше всего. Что происходит, когда вы меняете системную дату →

Проверка того, что граница дошла до программы

Тест, который проходит лишь потому, что программа так и не увидела 2038 год, хуже, чем отсутствие теста. Отчёт в конце каждой сессии говорит, какие часы программа читала и сколько раз:

Chrono Mock - session report
  target:   .\build\test.exe
  verdict:  WORKS - time substitution took effect  (processes: 1)
            every process that read time saw the session clock
  session:  fake clock reached 2038-01-19T03:14:08
            real elapsed 1.3s, fake elapsed 1.3s
  covered channels (substituted, with call counts, 10 calls in all):
            - pid 21240: GetSystemTimeAsFileTime (5 calls)
            - pid 21240: GetLocalTime (5 calls)

Строка session подтверждает, что часы приложения перешли границу. Вердикт works со счётчиками вызовов означает, что программа читала часы и получила дату 2038 года; works partially называет каналы, которые она читала другим путём. Что означают вердикты →

Чего пройденный тест не доказывает

  • Сдвинулись только собственные часы программы. Дата, которую она загружает из файла или базы данных, — та, что там записана, так что дата 2038 года внутри данных — это отдельный тест. Калькулятор дат вычислит такие даты за вас: chrono calc --base 2038-01-19T03:14:07 --zone +00:00 --shift +1s
  • Сервер, с которым общается программа, по-прежнему живёт в настоящей дате. 32-разрядный предел с той стороны недосягаем, а сессия предупреждает, когда видит, что программа открывает сетевое соединение.

Попробуйте