«Eppur Si Muove!»* Или Работа С Часовыми Поясами В Python

На нашей планете Земля одновременно в разных географических точках планеты может быть разное время суток.

Это следствие того, что наш мир представляет собой вращающийся геоид, а не плоский диск, и что в нашей Солнечной системе есть только одна звезда – Солнце.

Еще со школы все знают о часовых поясах, и все мы сталкивались с их проявлениями в реальной жизни («по Москве 15 часов, в Петропавловске-Камчатском полночь», смена часовых поясов на дальних рейсах и т. д.).

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

* «И всё же она вертится!» — крылатая фраза, предположительно произнесенная Галилео Галилеем после ухода из инквизиции после отказа от веры в то, что Земля вращается вокруг Солнца.

В нашем случае, к сожалению, такая ротация приводит ко всем этим «чудесным» проблемам с часовыми поясами.

Что общего между этой статьей и Галилеем? Да, в общем, ничего.

Боюсь, если бы наш мир был центром Вселенной, нам все равно пришлось бы иметь дело с часовыми поясами.

Будем считать заголовок моей ошибкой, которую я уже не могу исправить (хотя могу).

Что такое «часовой пояс»? Какой у вас часовой пояс? Если вы ответите «UTC+3», это будет правильный ответ только на текущий момент времени, но в целом это утверждение неверно.

Если вы посмотрите базу данных часовых поясов, то увидите, например, что Берлин и Вена, несмотря на смещение «UTC+1», имеют разные часовые пояса («Европа/Берлин» и «Европа/Вена»).

Почему это? Причина в том, что в разные моменты истории у них было разное летнее время (DST).

Даже если сегодня в этих двух странах и этих двух городах действуют одинаковые правила летнего времени, сто лет назад это было не так.

Например, и в Австрии, и в Германии не было перехода на летнее время в разные периоды времени: в Австрии с 1920 года, а в Германии с 1918 года.

Во время Второй мировой войны в обеих странах действовали одинаковые правила летнего времени (что неудивительно), но после ее окончания были снова рассинхронизация.

Германия отменила летнее время в 1949 году и вновь ввела его в 1979 году, а Австрия отменила летнее время в 1948 году и вновь ввела его в 1980 году.

Хуже всего то, что они даже не договорились об одной и той же дате перехода на летнее время.

И это происходит во всем мире.

Для компьютерных вычислений переход на летнее время является огромной проблемой, поскольку мы предполагаем, что время движется непрерывно и монотонно.

При переходе на летнее время каждый год есть час, который повторяется дважды, а есть час, который мы просто пропускаем.

Если при записи в журнал указать местное время, порядок строк журнала может нарушиться при сортировке.

Цитата из документации pytz:

Так, например, в часовом поясе США/Восточного времени в 2002 году во время окончания летнего времени, 27 октября, время 01:30 наступало дважды, а во время начала летнего времени, 7 апреля, время 02:30 наступало дважды.

не пришел, т. к.

в 02:00 часы были переведены на час вперед.

Но часовые пояса хранят больше, чем просто правила перехода на летнее время.

Некоторые страны меняют часовые пояса, иногда даже не меняя летнее время.

Например, в 1915 году Варшава перешла на центральноевропейское время.

В результате в полночь 5 августа 1915 года часы были переведены на 24 минуты назад (в то время как в Варшаве действовало летнее время).

В общем, с часовыми поясами творится еще больший ад. Есть как минимум одна страна, часовой пояс которой в течение дня отличался из-за синхронизации времени 0:00 со временем восхода солнца.

Где здравый смысл? Есть здравый смысл и оно называется Всемирным координированным временем (UTC).

UTC — часовой пояс без перехода на летнее время и без каких-либо изменений в прошлом.

Однако в связи с тем, что наша Земля представляет собой вращающийся геоид и в мире есть вещи, которыми мы не можем управлять, возникает проблема с корректировкой секунд (високосных секунд).

Будет ли UTC учитывать коррекционные секунды (которые нерегулярны и поэтому достаточно проблематично учесть в расчетах) или нет (тогда каждый часовой пояс будет иметь разницу в несколько секунд с UTC) - насколько мне известно, пока не решено.

решено.

В любом случае, сейчас UTC — самый безопасный вариант. Из UTC вы можете перевести время в местное время для любого часового пояса.

Обратное преобразование, учитывая вышеизложенное, невозможно.

Итак, вот главное практическое правило, которое никогда вас не подведет:

Всегда храните и работайте со временем в формате UTC. .

Если вам нужно сохранить исходные данные, запишите их отдельно.

Никогда не сохраняйте местное время и часовой пояс!

В чем проблема? В общем, на этой статье следовало бы закончить.

Но, к сожалению, есть несколько вещей, о которых вам следует помнить, когда вы программируете на Python. Это наследие архитектурных решений тех давних времен, когда никто не думал о практическом использовании языка.

Мотивация имела значение, а здравый смысл — нет. Однажды по поводу архитектуры модуля datetime стандартной библиотеки Python были приняты следующие решения:

  1. Модуль datetime не должен хранить информацию о часовом поясе, поскольку часовые пояса меняются слишком часто.

  2. С другой стороны, модуль datetime должен позволять вам добавлять информацию о часовом поясе (tzinfo).

  3. Модуль datetime должен реализовывать следующие объекты: дата, время, дата+время, timedelta.
К сожалению, что-то пошло не так.

Основная проблема заключается в том, что объект datetime, к которому добавлена информация о часовом поясе (tzinfo), не будет взаимодействовать с объектом datetime без часового пояса:

  
   

>>> import pytz, datetime >>> a = datetime.datetime.utcnow() >>> b = datetime.datetime.utcnow().

replace(tzinfo=pytz.utc) >>> a < b Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't compare offset-naive and offset-aware datetimes

Если вы проигнорируете ужасный API, в котором вам нужно добавлять информацию о часовом поясе к объекту datetime, у вас все равно возникнут проблемы.

Когда вы работаете с объектами datetime в Python, вам рано или поздно придется добавлять или удалять tzinfo из всех мест вашей программы.

Другая проблема заключается в том, что у вас есть два способа создать объект datetime с текущим временем в Python:

>>> datetime.datetime.utcnow() datetime.datetime(2011, 7, 15, 8, 30, 55, 375010) >>> datetime.datetime.now() datetime.datetime(2011, 7, 15, 10, 30, 57, 70767)

Один возвращает время в формате UTC, другой — местное время.

Однако объект datetime не сообщит вам, что такое «локальное время» (поскольку он не имеет информации о часовом поясе, по крайней мере, до версии Python 3.3), и невозможно узнать, какой из этих объектов хранит время в формате UTC. Если вы преобразуете временную метку UNIX в объект datetime, вам также следует быть осторожным при использовании метода datetime.datetime.utcfromtimestamp, поскольку он принимает временную метку по местному времени.

Библиотека datetime также предоставляет объекты даты и времени, к которым добавлять tzinfo совершенно бесполезно.

Объект времени нельзя перенести в другой часовой пояс, так как для этого необходимо знать дату.

Объект даты обычно имеет смысл только для местного часового пояса, потому что «сегодня» для меня может быть «вчера» или «завтра» для вас — спасибо чудесному миру часовых поясов.

Итак, каковы рекомендации экспертов? Теперь мы знаем, кто виноват. Но что делать? Если оставить в стороне теоретические проблемы, возникающие только при работе с историческими датами в прошлом, вот вам несколько рекомендаций.

Если вам приходится работать с историческими датами, есть альтернативный модуль mxDateTime, который довольно хорошо спроектирован и даже поддерживает разные календари (григорианский и юлианский).



Используйте UTC внутри программы

Если вам нужно получить текущее время, всегда используйте datetime.datetime.utcnow().

Если вы получаете местное время от пользователя, всегда немедленно конвертируйте его в UTC. Если невозможно произвести однозначную конвертацию, сообщите об этом пользователю, не пытайтесь угадать его время вслепую.

Во время перехода на летнее время и обратно мой iPhone несколько раз не смог правильно изменить время.

Я знаю, когда это сделать, потому что мне нужно установить часы.



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

Может показаться хорошей идеей всегда добавлять информацию о часовом поясе к объектам datetime, но на самом деле гораздо лучше не делать этого.

Хорошим решением было бы использовать объект datetime без tzinfo и со временем UTC. Учтите тот факт, что вы не можете сравнивать время с часовым поясом со временем без него, так же как вы не можете смешивать байты и юникод в Python 3. Используйте этот недостаток API в своих целях.

  1. Внутри программы всегда используйте объекты datetime без tzinfo со временем UTC.
  2. Когда вы взаимодействуете с пользователем, всегда конвертируйте его местное время UTC и обратно.

Почему вам не нужно добавлять tzinfo к объекту datetime? Во-первых, потому что подавляющее большинство библиотек ожидают, что tzinfo будет None. Во-вторых, постоянно работать с tzinfo — ужасная идея, учитывая кривое API для работы с ним.

В библиотеке pytz есть альтернативные функции для преобразования часовых поясов, поскольку API преобразования tzinfo, реализованный в стандартной библиотеке, недостаточно гибок для работы с большинством реальных часовых поясов.

Если мы не будем использовать объекты tzinfo, есть вероятность, что в будущем ситуация изменится к лучшему.

Другая причина не использовать время с часовым поясом заключается в том, что объект tzinfo очень специфичен и сильно зависит от реализации.

Не существует стандартного способа передачи информации о часовом поясе (за возможным исключением часового пояса UTC) на другие языки, через HTTP и т. д. Кроме того, объекты datetime с информацией о часовом поясе часто становятся слишком огромными при сериализации с использованием модуля Pickle, или они могут не работать.

даже быть сериализуемым (это зависит от реализации объекта tzinfo).



Форматирование преобразований

Если вам нужно отобразить время в часовом поясе пользователя, возьмите объект datetime UTC, добавьте к нему часовой пояс UTC, преобразуйте время в местное время пользователя и отформатируйте его.

Не используйте преобразование часовых поясов с помощью методов tzinfo, поскольку они работают неправильно, используйте pytz. Затем преобразуйте время в «наивное», отбросив смещение часового пояса из полученного объекта datetime, который вы создали для форматирования, и продолжайте свою жизнь.

Переведено Дредатур , текст читается как %username%.






Бонус от переводчика для тех, кто дочитал до конца:

Отличное видео от Тома Скотта о часовых поясах: И в следующая статья Я напишу, где автор не прав, почему он не прав и как еще нужно сделать правильно.

Теги: #python #программирование #datetime #timezone #pytz #made of Steel

Вместе с данным постом часто просматривают: