В практической разработке ни одно из убеждений нельзя принимать за аксиому. Не потому, что утверждения ложны — многие из них были бы верны, если бы не досадные баги и пограничные случаи, которых за историю создания ПО накопилось несметное число.
Наверняка вы считаете, что большинство этих утверждений верны?
- В сутках всегда 24 часа.
- В каждой минуте — 60 секунд.
- Неделя всегда начинается и заканчивается в том же месяце.
- Неделя (или месяц) всегда начинается и заканчивается в том же году.
- У времени нет начала и конца.
- Продолжительность месяца варьируется от 28 до 31 дня.
- В каждом месяце везде всегда одно и то же количество дней.
- Високосный год — раз в 4 года.
- На сервере и на клиенте всегда одно и то же время.
- Количество часов и минут с какого-то момента времени легко вычислить.
А вот у программистов на этот счёт совсем другое мнение.
В каждой минуте не всегда 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 года, когда количество секунд с начала отсчёта достигнет 2³¹. Это число системы могут счесть отрицательным.
Всё это — малая часть нюансов и багов, с которыми сталкивается разработчик при создании ПО, где нужен учёт времени. Вам наверняка тоже есть что рассказать об этом — поделитесь своим опытом в комментариях.