Некоторые программистские заблуждения о времени

so60jmabh0uc6s5espyztmjkl0u.jpeg

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

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

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


К примеру, в старых версиях KVM в CentOS был любопытный баг — минута могла длиться сколько угодно. Дело в том, что KVM не знала, что она исполняется не на физическом оборудовании, и если хостинговая ОС временно приостанавливала виртуальную машину, то виртуализированные часы продолжали идти с момента приостановки. Например, если машина вставала на паузу в 13:00 и активировалась в 15:00, системные часы в ней продолжали показывать 13:00. В результате, после каждой приостановки возникало расхождение с реальным временем. Была даже cron-задача, которую можно было установить для синхронизации виртуализированных часов с аппаратными. Но при создании новой виртуалки легко можно было об этом забыть, и это было весело. Позднее баг исправили.

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

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

А если прибавить к этому смену часовых поясов…

6d7b580550aefd9252d8cad9bc4c5910.jpg

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


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

Считаете, что это только у евреев всё не как в григорианском календаре (к вопросу о Великой октябрьской социалистической революции, которую в СССР праздновали 7 ноября)? В арабских странах:

  • Неделя начинается не в понедельник, а в воскресенье.
  • Выходными считаются пятница и суббота, а в некоторых странах — четверг и пятница. За последние 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 года, когда количество секунд с начала отсчёта достигнет 231, а это число системы могут посчитать отрицательным.
f65a0aff84a562bc56ce0368b48cabed.jpg

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

© Habrahabr.ru