Некоторые программистские заблуждения о времени
Наверняка вы уверены, что какие-то —, а то и все — из этих утверждений истинны всегда и везде:
- В сутках всегда 24 часа.
- Неделя всегда начинается и заканчивается в том же месяце.
- Неделя (или месяц) всегда начинается и заканчивается в том же году.
- У времени нет начала и конца.
- В месяце может быть 28, 29, 30 или 31 день.
- Високосный год бывает раз в 4 года.
- В каждом месяце везде всегда одно и тоже количество дней.
- На сервере и на клиенте всегда будет одно и то же время.
- Можно легко вычислить количество часов и минут начиная с какого-то момента времени.
- В каждой минуте 60 секунд.
Хотя практика показывает, что полагаться на истинность этих заявлений при разработке ПО — заблуждение. Ну ладно, не все эти пункты относятся к заблуждениям. Некоторые утверждения были бы верны, если бы не досадные баги и пограничные случаи, которых за историю ПО накопилось несметное количество.
В каждой минуте 60 секунд, а в сутках всегда 24 часа
К примеру, в старых версиях KVM в CentOS был любопытный баг — минута могла длиться сколько угодно. Дело в том, что KVM не знала, что она исполняется не на физическом оборудовании, и если хостинговая ОС временно приостанавливала виртуальную машину, то виртуализированные часы продолжали идти с момента приостановки. Например, если машина вставала на паузу в 13:00 и активировалась в 15:00, системные часы в ней продолжали показывать 13:00. В результате, после каждой приостановки возникало расхождение с реальным временем. Была даже cron-задача, которую можно было установить для синхронизации виртуализированных часов с аппаратными. Но при создании новой виртуалки легко можно было об этом забыть, и это было весело. Позднее баг исправили.
Если же отвлечься от багов, то существует ещё и високосная (дополнительная) секунда: она добавляется к UTC (всемирному скоординированному времени) в конце суток 30 июня или 31 декабря, чтобы компенсировать постепенное замедление вращения Земли и накопление разницы между сутками в системе СИ и астрономическими — солнечными — сутками.
Что же касается длительности суток, то главный враг программиста — переход на летнее время, который где-то есть, где-то его нет, причём раньше его могли отменить или ввести. Это отдельная большая проблема исторических данных — учёт летнего времени.
А если прибавить к этому смену часовых поясов…
Про недели, месяцы и годы
А вот истории насчёт разной длительности месяцев и лет связаны с разным календарным исчислением у разных народов мира. К примеру, еврейский календарь оперирует лунными месяцами: то есть начало и конец месяца привязаны к фазам Луны. Думаете, можно это легко учесть, добавив для Израиля корректировку с подгрузкой лунных фаз? Не выйдет. В еврейском високосном году добавляется лишний месяц. Более того, — следите за руками — и простой, и високосный год в еврейском календаре может иметь три разных длительности. Итого год может иметь шесть разных длительностей от 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, а это число системы могут посчитать отрицательным.
Всё это — лишь малая часть огромного количества нюансов и багов, с которыми приходится справляться разработчикам любых продуктов, использующих время. Наверняка вам тоже есть что рассказать из своего опыта, пишите в комментариях.