Найти в Дзене
VK Team

Парадоксы времени от программистов: почему в сутках не 24 часа

Оглавление

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

Изображение: Freepik
Изображение: Freepik

Наверняка вы считаете, что большинство этих утверждений верны?

  • В сутках всегда 24 часа.
  • В каждой минуте — 60 секунд.
  • Неделя всегда начинается и заканчивается в том же месяце.
  • Неделя (или месяц) всегда начинается и заканчивается в том же году.
  • У времени нет начала и конца.
  • Продолжительность месяца варьируется от 28 до 31 дня.
  • В каждом месяце везде всегда одно и то же количество дней.
  • Високосный год — раз в 4 года.
  • На сервере и на клиенте всегда одно и то же время.
  • Количество часов и минут с какого-то момента времени легко вычислить.

А вот у программистов на этот счёт совсем другое мнение.

В каждой минуте не всегда 60 секунд, а в сутках не всегда 24 часа

Старые версии KVM в CentOS содержали занятный баг — минута могла длиться сколько угодно. KVM не знала, что она исполняется не на физическом оборудовании, и если хостинговая ОС временно приостанавливала виртуальную машину, то виртуализированные часы продолжали идти с момента приостановки.

Например, если машина вставала на паузу в 13:00 и активировалась в 15:00, системные часы в ней продолжали показывать 13:00. То есть после каждой приостановки время расходилось с реальным. Появилась даже cron-задача, которую можно было установить для синхронизации виртуализированных часов с аппаратными. Но при создании новой виртуалки о проблеме часто забывали, и получалось весело. Позднее ошибку исправили.

Если же отвлечься от багов, есть ещё и високосная (дополнительная) секунда, которую добавляют к UTC (всемирному координированному времени) в конце суток 30 июня или 31 декабря. Это позволяет компенсировать постепенное замедление вращения Земли и накопление разницы между сутками в СИ и астрономическими — солнечными — сутками.

Что касается длительности суток, главный враг программиста — переход на летнее время. Где-то он есть, где-то его нет, причём раньше его могли отменить или ввести. Поэтому учёт летнего времени — отдельная большая проблема исторических данных.

Прибавим к этому смену часовых поясов — и получим весёлый квест на учёт всех возможных погрешностей.

Про недели, месяцы и годы

Всё, что связано с длительностью месяцев и лет, отсылает нас к разным исчислениям календарной даты у разных народов. К примеру, еврейский календарь оперирует лунными месяцами, начало и конец которых привязаны к фазам Луны. Думаете, это легко учесть, добавив для Израиля корректировку с подгрузкой лунных фаз? Не всё так просто. В еврейском високосном году добавляется ещё один месяц. Более того, — следите за руками — и простой, и високосный год в еврейском календаре может иметь три разные длительности. Всего же год может иметь шесть разных длительностей от 354 до 383 дней. Но на этом отличия от нашего календаря не заканчиваются. В еврейском календаре сутки имеют разную длительность и отсчитываются от заката до заката, формально — когда на небе становятся видны три звезды.

Не только еврейский календарь так сильно отличается от григорианского (к вопросу о Великой Октябрьской социалистической революции, которую в СССР праздновали 7 ноября).

Изображение: 2H Media / Unsplash
Изображение: 2H Media / Unsplash

Ещё больше нюансов нужно учитывать при расчёте времени в арабских странах:

  • Неделя начинается не в понедельник, а в воскресенье.
  • Выходными считаются пятница и суббота, а в некоторых странах — четверг и пятница.
  • За последние 10 лет некоторые государства перенесли выходные на пятницу и субботу, чтобы способствовать международной торговле.
  • И в дополнение: не во всех арабских странах выходные состоят из двух дней. Религиозные праздники зависят от наблюдений за лунным циклом, поэтому их сроки наступления и завершения не могут быть точно предсказаны.

Теперь о неделях и месяцах, которые могут заканчиваться в следующем году. Это характерно для стран с лунными и солнечно-лунными календарями — в них новый год наступает не 1 января. Навскидку можно вспомнить китайский календарь. В Китае новый год наступает где-то в промежутке между 21 января и 21 февраля по григорианскому календарю. А в эфиопском календаре новый год вообще 29 или 30 августа, да ещё и количество лет у них на 8 меньше, чем в системе от Рождества Христова.

На сервере и на клиенте не всегда одно и то же время

А вот интересный нюанс, который может вести не только к показу разного времени на разных компьютерах, но и к отличиям в размере такого расхождения. При загрузке Linux берёт текущее аппаратное время и обновляет его на основе данных от внутренних часов процессора (TSC). Эти часы могут быть неточны, например, из-за масштабирования тактовой частоты, которая динамически меняет частоту TSC. Если поменять частоту часов на хосте, гостевые аккаунты даже не заметят этого. Если масштабировать частоту TSC на 50%, время начнёт идти вдвое медленнее. Кроме того, на некоторых серверах BIOS может масштабировать частоту процессора без уведомления ОС, что тоже добавляет погрешность. На более современных процессорах частоты TSC фиксированы. К слову, Windows не использует TSC, так что в этой ОС другие проблемы с отсчётом времени :)

Количество часов и минут, прошедших с конкретного момента времени не так уж и легко вычислить

Если в языке программирования нет чего-то вроде питоновской tzinfo(), вы не сможете получить конкретную дату и время, просто добавив часы и минуты к какой-нибудь дате в прошлом. Нужно учесть временные зоны (и их возможную смену, как это не раз происходило в истории), затем учесть все изменения в переходе на летнее время. В Windows это невозможно, потому что Microsoft предоставляет только начало и конец текущего года. Удивительно, что после стольких патчей обработки перехода на летнее время компания до сих пор не реализовала в Windows аналог tzinfo(). Вероятно, и не реализует.

У времени есть начало и конец

«Вашей программе никогда не понадобится обрабатывать даты до 1970 года». В Unix-системах (в том числе Linux и iOS) время отсчитывается в количестве секунд, начиная с 00:00:00 1 января 1970 года по UTC (всемирному координированному времени). Всё, что было раньше, в Unix будет уже отрицательным временем. Причём время представлено в 32-битном целочисленном выражении. Самая ранняя дата, допустимая в Unix-системах, — 13 декабря 1901 года. «Потолок» Unix-времени — 19 января 2038 года, когда количество секунд с начала отсчёта достигнет 2³¹. Это число системы могут счесть отрицательным.

Изображение: Lukas Blazek / Unsplash
Изображение: Lukas Blazek / Unsplash

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

Карьера в VK