<?xml version="1.0" encoding="utf-8" ?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:tt="http://teletype.in/" xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"><title>Закладки разработчика | IT Notes</title><author><name>Закладки разработчика | IT Notes</name></author><id>https://teletype.in/atom/itnotes</id><link rel="self" type="application/atom+xml" href="https://teletype.in/atom/itnotes?offset=0"></link><link rel="alternate" type="text/html" href="https://teletype.in/@itnotes?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=itnotes"></link><link rel="next" type="application/rss+xml" href="https://teletype.in/atom/itnotes?offset=10"></link><link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></link><updated>2026-09-22T15:25:54.496Z</updated><entry><id>itnotes:python-in-time</id><link rel="alternate" type="text/html" href="https://teletype.in/@itnotes/python-in-time?utm_source=teletype&amp;utm_medium=feed_atom&amp;utm_campaign=itnotes"></link><title>Как не разбудить пользователя посреди ночи? Самая полная статья о работе со временем в Python.</title><published>2022-05-13T16:47:36.641Z</published><updated>2022-05-13T16:47:36.641Z</updated><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img3.teletype.in/files/af/2e/af2ea2ae-1a28-46de-8b20-d27c56f125cf.png"></media:thumbnail><summary type="html">&lt;img src=&quot;https://img1.teletype.in/files/40/1a/401ab4e0-fff2-4437-97e9-e2981da1561c.png&quot;&gt;Мне даже сложно описать как часто бизнес ставит программам условные ограничения связанные со временем и временными зонами наших пользователей, а ещё сложнее описать как часто программисты вообще забывают о том, что такие ограничения действительно должны существовать, дабы улучшить пользовательский опыт или, вовсе не навредить пользователю, например, отправив ему уведомление в 4 часа ночи!</summary><content type="html">
  &lt;h2 id=&quot;3CTF&quot;&gt;Проблематика и мотивации&lt;/h2&gt;
  &lt;p id=&quot;Z4Gf&quot;&gt;Мне даже сложно описать как часто бизнес ставит программам условные ограничения связанные со временем и временными зонами наших пользователей, а ещё сложнее описать как часто программисты вообще забывают о том, что такие ограничения действительно должны существовать, дабы улучшить пользовательский опыт или, вовсе не навредить пользователю, например, отправив ему уведомление в 4 часа ночи!&lt;/p&gt;
  &lt;p id=&quot;4Lqs&quot;&gt;Изучение времени и того, как оно работает, волновало людей уже сотни (если не тысячи) лет, но со “временем” человечество придумало &lt;em&gt;пружинные двигатели&lt;/em&gt; (основу всех механических часов), а позже и &lt;em&gt;кварцевые резонаторы&lt;/em&gt; (используются в современных электронных и кварцевых часах).&lt;/p&gt;
  &lt;h2 id=&quot;zBhY&quot;&gt;Как компьютер узнаёт время?&lt;/h2&gt;
  &lt;p id=&quot;qptu&quot;&gt;Этот раздел вы можете смело пропустить, ведь ответ до ужаса банален - из интернета, почти всегда. Но что компьютер делает, когда интернета нет и как понимает сколько времени прошло с того момента, как его выключили? Ведь современные компьютеры не просят вас настраивать время при каждом запуске, если подключения к интернету у них нету.&lt;/p&gt;
  &lt;p id=&quot;yqMA&quot;&gt;Большинство современных девайсов использует всё те же кварцевые резонаторы (как внешний генератор тактовых сигналов) или получает сигнал из питающей электросети, но одним только резонатором дело не обходится - тактовая частота требует преобразования, чем занимается так называемая RTC (Real Time Clock) микросхема. Она преобразует тактовую частоту кварцевого резонатора в разные типы времени - календарный, по типу YY-MM-DD и HH-MM-SS, а также в бинарный, в виде счётчика единиц времени - секунд или их долей.&lt;/p&gt;
  &lt;p id=&quot;YSKu&quot;&gt;Питает всё это чудо маленькая плоская батарейка, которая устанавливается в материнскую плату девайса. Даже в электронных часах есть такой “запасной” питающий элемент, который может служить определённое количество времени. Эта же батарейка питает систему хранения настроек материнской платы, в том числе BIOS’a, поэтому многим может быть известна схема сброса этих настроек - вынуть батарейку и вставить назад. Тогда и время слетит.&lt;/p&gt;
  &lt;figure id=&quot;obt6&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img1.teletype.in/files/40/1a/401ab4e0-fff2-4437-97e9-e2981da1561c.png&quot; width=&quot;640&quot; /&gt;
    &lt;figcaption&gt;А вот пример этой самой RTC, которую питает плоская батарейка - CR2032.&lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;h2 id=&quot;BUon&quot;&gt;Python: всё о работе со временем!&lt;/h2&gt;
  &lt;p id=&quot;Wgt5&quot;&gt;В Python существует несколько связанных со временем модулей - &lt;strong&gt;&lt;em&gt;time&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;datetime&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;dateutil&lt;/em&gt;&lt;/strong&gt;. Стоит по отдельности разобрать каждый из них, так как все они решают свои особенные проблемы, хотя и понимают друг друга на уровне абстракций.&lt;/p&gt;
  &lt;h3 id=&quot;Zku4&quot;&gt;Модуль time&lt;/h3&gt;
  &lt;p id=&quot;vCWC&quot;&gt;Несмотря на то, что именно модуль &lt;strong&gt;&lt;em&gt;time&lt;/em&gt;&lt;/strong&gt; является базовым средством работы со временем в Python, он часто уступает в удобстве своему младшему брату - модулю &lt;strong&gt;&lt;em&gt;datetime&lt;/em&gt;&lt;/strong&gt;, и большинству разработчиков известен скорее из за своей блокирующей функции &lt;code&gt;time.sleep(seconds: int)&lt;/code&gt;, которая заставляет программу “застыть” в ожидании окончания секундного таймера.&lt;/p&gt;
  &lt;p id=&quot;K6wQ&quot;&gt;Первое, что нам необходимо - узнать сколько сейчас времени. Это можно сделать при помощи функции &lt;code&gt;time.localtime()&lt;/code&gt;, что мы и сделаем:&lt;/p&gt;
  &lt;pre id=&quot;ey5q&quot; data-lang=&quot;python&quot;&gt;import time

time.localtime()
# time.struct_time(tm_year=2022, tm_mon=5, tm_mday=10, tm_hour=23, tm_min=0, tm_sec=18, tm_wday=2, tm_yday=131, tm_isdst=0)
# В ответе мы получили обьект типа time.struct_time, это точное время, когда я пишу эту статью!

time.localtime(0)
# time.struct_time(tm_year=1970, tm_mon=1, tm_mday=1, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=1, tm_isdst=0)
# Мы получаем обьект с установленным временем в 00:00:00 1 Января 1970-го
# Эта дата - начало отсчёта UNIX-времени!

time.ctime()  # Wed May 11 23:00:00 2022
# Удобочитаемый формат времени

time.ctime(0)  # Thu Jan  1 00:00:00 1970
# Если в вашей системе время настроено на Московский или любой другой отличный от UTC часовой пояс
# то вы получите немного отличный результат, обратите на это внимание!&lt;/pre&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;sSUF&quot;&gt;В примерах может присутствовать несущественное расхождение в результатах выполнения функций! Для удобочитаемости я округлял результаты работы функций, а также приводил все даты в примерах к одной.&lt;/p&gt;
  &lt;/section&gt;
  &lt;p id=&quot;092m&quot;&gt;Иногда вместо объекта времени нам необходимо получить так называемый &lt;em&gt;timestamp&lt;/em&gt; (тайм-cтемп) - количество секунд прошедших с 00:00:00 (для UTC) 1 Января 1970-го года:&lt;/p&gt;
  &lt;pre id=&quot;9fq6&quot; data-lang=&quot;python&quot;&gt;time.time()  # 1652300066.808788
# Это количество секунд прошедших с полуночи 1 Января 1970-го!&lt;/pre&gt;
  &lt;p id=&quot;uf2w&quot;&gt;А теперь немного поиграемся с часовыми поясами: попробуем вывести текущее время в виде строки и в виде тайм-cтемпа, а потом сменим часовой пояс и повторим то же самое:&lt;/p&gt;
  &lt;pre id=&quot;cnzp&quot; data-lang=&quot;python&quot;&gt;# Узнаем текущий часовой пояс
time.tzname    # (&amp;#x27;MSK&amp;#x27;, &amp;#x27;MSK&amp;#x27;)
time.timezone  # -10800 | Смещение часового пояса в секундах (10800 секунд = 3 часа)

# Выводим текущее время в системе
time.time()   # 1652300626.184014
time.ctime()  # Wed May 10 23:30:00 2022

# Для установки часового пояса понадобится модуль os, импортируйте его к себе
os.environ[&amp;#x27;TZ&amp;#x27;] = &amp;#x27;UTC&amp;#x27;
time.tzset()

# Снова выведем время
time.time()   # 1652300626.184296
time.ctime()  # Wed May 10 20:30:00 2022&lt;/pre&gt;
  &lt;p id=&quot;lYaH&quot;&gt;У неопытного разработчика это может вызвать диссонанс - время разное, но тайм-стемп одинаковый. Это потому, что тайм-стемп всегда считается с учётом часового пояса, поэтому для МСК отсчёт секунд будет начинаться не в полночь (00:00:00 1 Января 1970-го), а в 3 часа ночи!&lt;/p&gt;
  &lt;p id=&quot;zXbf&quot;&gt;Также в модуле &lt;em&gt;&lt;strong&gt;time&lt;/strong&gt;&lt;/em&gt; есть функция позволяющая замерить количество процессорного времени, затраченного процессом до времени замера - &lt;code&gt;time.process_time()&lt;/code&gt;, но я вернусь к этой функции в своей следующей статье о тестировании Python приложений на производительность!&lt;/p&gt;
  &lt;h3 id=&quot;b4xV&quot;&gt;Модуль datetime&lt;/h3&gt;
  &lt;p id=&quot;Ejsq&quot;&gt;Несмотря на всю практичную простоту модуля &lt;strong&gt;&lt;em&gt;time&lt;/em&gt;&lt;/strong&gt;, иногда нам нужно что-то более &lt;s&gt;комплексное&lt;/s&gt; умное. Об этом также подумали разработчики модуля &lt;strong&gt;&lt;em&gt;datetime&lt;/em&gt;&lt;/strong&gt;, создав удобную обёртку вокруг модуля &lt;em&gt;&lt;strong&gt;time&lt;/strong&gt;&lt;/em&gt;. Узнать об этом можно открыв сам файлик &lt;em&gt;datetime.py &lt;/em&gt;и просмотрев какие модули он импортирует:&lt;/p&gt;
  &lt;pre id=&quot;2tPM&quot; data-lang=&quot;python&quot;&gt;import time as _time  # &amp;lt;- Вот он заветный!
import math as _math
import sys
from operator import index as _index&lt;/pre&gt;
  &lt;p id=&quot;k5px&quot;&gt;Остальные модули, которые импортирует &lt;em&gt;&lt;strong&gt;datetime&lt;/strong&gt;&lt;/em&gt; нам не интересны.&lt;/p&gt;
  &lt;p id=&quot;7AAg&quot;&gt;Основной функционал данного модуля предоставляется через четыре класса - &lt;code&gt;date&lt;/code&gt;, &lt;code&gt;datetime&lt;/code&gt; и &lt;code&gt;timedelta&lt;/code&gt;. Названия первых двух классов говорят сами за себя, но третий часто вызывает вопросы у тех, кто впервые с ним сталкивается. Давайте разберём каждый из классов по отдельности и сравним их работу с модулем &lt;em&gt;&lt;strong&gt;time&lt;/strong&gt;&lt;/em&gt;!&lt;/p&gt;
  &lt;blockquote id=&quot;o36n&quot;&gt;Если изучить исходники модулей &lt;strong&gt;&lt;em&gt;time&lt;/em&gt;&lt;/strong&gt; и &lt;strong&gt;&lt;em&gt;datetime&lt;/em&gt;&lt;/strong&gt; и сравнить их, то можно увидеть, что модуль &lt;strong&gt;&lt;em&gt;time&lt;/em&gt;&lt;/strong&gt; вообще не предоставляет никакого функционала на Python, он использует написанные на C++ модули для возвращения результата, тогда как модуль &lt;strong&gt;&lt;em&gt;datetime&lt;/em&gt;&lt;/strong&gt; написан именно на Python.&lt;/blockquote&gt;
  &lt;p id=&quot;dyj0&quot;&gt;&lt;strong&gt;Работа с датами&lt;/strong&gt;&lt;/p&gt;
  &lt;pre id=&quot;Gwwd&quot; data-lang=&quot;python&quot;&gt;import time as _time
from datetime import date

# Попробуем вывести в читаемом виде сегодняшнюю дату
_time.ctime(_time.time())                      # Thu May 10 19:00:30 2022
date.today().ctime()                           # Thu May 12 00:00:00 2022

# А теперь решим классическую задачу: узнаем является ли сегодняшний день 9-м мая
_time.localtime().tm_mday == 9 and _time.localtime().tm_mon == 5  # False
date.today().day == 9 and date.today().month == 5                 # False
# Увы, но сегодня не 9-е мая...

# Попробуем получить дату из строки
_time.strptime(&amp;#x27;2022/05/10&amp;#x27;, &amp;#x27;%Y/%m/%d&amp;#x27;) # time.struct_time(tm_year=2022, tm_mon=5, tm_mday=10)
date.strptime(&amp;#x27;2022/05/10&amp;#x27;, &amp;#x27;%Y/%m/%d&amp;#x27;)  # AttributeError...
# Да, через класс date мы не сможем получить дату из строки
# Но мы сможем сделать это используя класс datetime, об этом ниже!

# Зато получить дату через таймстемп мы можем
ts = _time.time()  # Получаем количество секунд (мы могли бы передать их числом)
_time.localtime(ts)         # time.struct_time(tm_year=2022, tm_mon=5...)
date.fromtimestamp(ts)      # 2022-05-10&lt;/pre&gt;
  &lt;p id=&quot;lTWa&quot;&gt;Несмотря на то, что модуль time в некоторых местах показывает себя лучше, чем функционал класса &lt;code&gt;datetime.date&lt;/code&gt;, сам класс всё же бывает полезным в случаях, когда нам приходится работать с датой отдельно от времени.&lt;/p&gt;
  &lt;p id=&quot;KFdl&quot;&gt;&lt;strong&gt;Лучшее, что есть &amp;quot;из коробки&amp;quot;&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;o4TS&quot;&gt;Заранее извиняясь за тавтологию, перейду к рассмотрению жемчужины модуля &lt;strong&gt;&lt;em&gt;datetime&lt;/em&gt;&lt;/strong&gt; - классу &lt;code&gt;datetime&lt;/code&gt;! Он предоставляет куда более гибкий интерфейс для работы с датой и временем, чем модуль &lt;em&gt;&lt;strong&gt;time&lt;/strong&gt;&lt;/em&gt;:&lt;/p&gt;
  &lt;pre id=&quot;EtYX&quot; data-lang=&quot;python&quot;&gt;import time as _time
from datetime import datetime

# Начнём с того, что снова узнаем сколько сейчас времени
_time.localtime()  # time.struct_time(tm_year=2022, tm_mon=5...)
datetime.now()     # 2022-05-10 19:00:00.746417

# Снова попробуем получить дату и время из строки
_time.strptime(&amp;#x27;2022/05/10&amp;#x27;, &amp;#x27;%Y/%m/%d&amp;#x27;)     # time.struct_time(tm_year=2022, tm_mon=5...)
datetime.strptime(&amp;#x27;2022/05/10&amp;#x27;, &amp;#x27;%Y/%m/%d&amp;#x27;)  # 2022-05-10 00:00:00
# Теперь мы получаем не ошибку, а обьект класса datetime

# А можно и наоборот, преобразовать обьекты в строку нужного нам вида
_time.strftime(&amp;#x27;Year: %Y, Month: %m, Day: %d&amp;#x27;, _time.localtime())  
# Year: 2022, Month: 05, Day: 10
datetime.now().strftime(&amp;#x27;Year: %Y, Month: %m, Day: %d&amp;#x27;)  
# Year: 2022, Month: 05, Day: 10

# Сравним как получить таймстемп через datetime
_time.time()                # 1652372410.6999412
datetime.now().timestamp()  # 1652372410.700101

# Попробуем изменить день в обьекте datetime и снова получить таймстемп
datetime.now().timestamp()                  # 1652372637.027454
datetime.now().replace(day=11).timestamp()  # 1652286237.027475
# Мы потеряли приблезительно сутки - 86399 секунд = 23.9 часа&lt;/pre&gt;
  &lt;p id=&quot;Z5l5&quot;&gt;&lt;strong&gt;Арифметика дат и времени&lt;/strong&gt;&lt;/p&gt;
  &lt;p id=&quot;Y2RE&quot;&gt;Нередко бывает так, что для реализации бизнес-логики нам необходимо не только знать конкретные даты и время, но и работать с их арифметикой - вычитать из даты часы, узнавать разницу между двумя датами, хранить временные отрезки и так далее. Для решения всех этих задач был разработан класс &lt;code&gt;timedelta&lt;/code&gt;, представляющий собой некий временной промежуток, который можно интерпретировать как дни, секунды и микросекунды (одна миллионная секунды).&lt;/p&gt;
  &lt;pre id=&quot;SDvu&quot; data-lang=&quot;python&quot;&gt;from datetime import datetime, timedelta

# Простой пример того, как timedelta упрощает вычитание дня из определённой даты
datetime.now().replace(day=datetime.today().day - 1)  # 2022-05-9 20:15:00
datetime.now() - timedelta(days=1)                    # 2022-05-9 20:15:00

# Но нагляднее всего timedelta проявляет себя, когда надо вычесть первый день месяца
# или наоборот прибавить одинь день к 31-му декабря
datetime.today().replace(day=1) - timedelta(days=1)        # 2022-04-30 20:30:00
datetime(year=2021, month=12, day=31) + timedelta(days=1)  # 2022-01-01 00:00:00

# В случае вычитания дат друг из друга мы тоже получим обьект timedelta
time = (datetime.today() + timedelta(days=1)) - datetime.today()
type(time)  # &amp;lt;class &amp;#x27;datetime.timedelta&amp;#x27;&amp;gt;

# Сравнивать даты можно также как и любые другие обьекты в Python
datetime.now() == datetime.now()  # False
datetime.now() &amp;lt; datetime.now()   # True
# Это может сбивать с толку, т.к. кажется, что функции выполняются одновременно,
# но по правилам алгебры логики выражения выполняются слева-направо

# Сами обьекты timedelta тоже можно сравнивать
timedelta(days=1) == timedelta(seconds=86400)  # True
timedelta(seconds=1) &amp;lt; timedelta(seconds=1)    # False&lt;/pre&gt;
  &lt;h2 id=&quot;UPSc&quot;&gt;Проблема часовых поясов&lt;/h2&gt;
  &lt;p id=&quot;42AL&quot;&gt;Встроенные модули, о которых мы сейчас говорили, умеют работать с разными часовыми поясами, но для этого нужно глобально выставлять часовой пояс системы или процесса через переменные окружения, или вовсе передавать часовой пояс при создании обьекта времени.&lt;/p&gt;
  &lt;p id=&quot;Tg1c&quot;&gt;Вместо того, чтобы шутить тут про Мишу из Москвы и Юру из Уфы, я лучше попытаюсь вам обьяснить, насколько всё в действительности сложно. Для начала, нам нужно определиться с понятиями, с тем, что такое часовой пояс (timezone) и с тем, что такое отступ (offset) в контексте часовых поясов.&lt;/p&gt;
  &lt;p id=&quot;bCKB&quot;&gt;Отступом называют разницу между часовыми поясами и обозначают как “+N” или “-N” относительно Всемирного координированного времени (UTC). Таким образом Москва живёт с отступом UTC+3, а Уфа с отступом UTC+5. Но существуют страны, которые не придерживаются стандартного 24-х часового круга часовых поясов и используют свои уникальные отступы. Так в одной только Австралии встречаются часовые пояса с отступами: UTC+8:45, UTC+9:30 и UTC+10:30. В столице Либерии (Африка) до 1979-го года часовой пояс шел с отступом в 44 минуты и 30 секунд, а записывался как UTC+00:44:30!&lt;/p&gt;
  &lt;figure id=&quot;KwtG&quot; class=&quot;m_original&quot;&gt;
    &lt;img src=&quot;https://img2.teletype.in/files/18/38/18384fd4-6bc6-410b-8536-d9829b59d7df.png&quot; width=&quot;4000&quot; /&gt;
  &lt;/figure&gt;
  &lt;p id=&quot;4uJ4&quot;&gt;Но отступ и часовой пояс - не одно и то же. Часовой пояс - территория, именуемая в зависимости от своего географического местонахождения. Москва живёт по часовому поясу “Europe/Moscow”, а Нью-Йорк по поясу “America/New_York”. Часовых поясов существует неимоверное множество, они ежегодно обновляются и меняются с учётом различных обстоятельств: скорости вращения Земли, смены правительства, разделения территорий, религиозных взглядов и т.д. Найти полный список можно &lt;a href=&quot;https://en.wikipedia.org/wiki/List_of_tz_database_time_zones&quot; target=&quot;_blank&quot;&gt;тут&lt;/a&gt;.&lt;/p&gt;
  &lt;p id=&quot;Jclo&quot;&gt;Думаю к этому моменту вы уже осознали всю боль, с которой сталкивались разработчики первых международных программ, но именно благодаря им нам не приходится ежедневно разбирать гневные письма со всех уголков земли о том, что наша программа работает неправильно, ведь у нас есть множество готовых решений по работе с часовыми поясами, одним из которых является пакет &lt;strong&gt;&lt;em&gt;dateutil&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
  &lt;section style=&quot;background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);&quot;&gt;
    &lt;p id=&quot;z32X&quot;&gt;Сам по себе пакет dateutil представляет очень мощное расширение стандартного модуля datetime, предоставляя набор инструментов для работы с часовыми поясами, парсингом времени, генераторами дат. Очень подробно об этом написано в документации модуля, правда она на английском - &lt;a href=&quot;https://dateutil.readthedocs.io&quot; target=&quot;_blank&quot;&gt;https://dateutil.readthedocs.io&lt;/a&gt;&lt;/p&gt;
  &lt;/section&gt;
  &lt;h2 id=&quot;oxuQ&quot;&gt;Пакет dateutil&lt;/h2&gt;
  &lt;p id=&quot;Kfbo&quot;&gt;Думаю мне не стоит растягивать материал обьяснением виртуальных окружений и установкой сторонних пакетов, поэтому я просто попрошу вас установить нужный нам пакет командой &lt;code&gt;pip install python-dateutil&lt;/code&gt; и просто приступить у его рассмотрению:&lt;/p&gt;
  &lt;pre id=&quot;cvAx&quot; data-lang=&quot;python&quot;&gt;from datetime import datetime
from dateutil import tz  # &amp;lt;- Этот модуль отвечает за работу с таймзонами

# Начнём с того, что узнаем в каком часовом поясе мы находимся
dt = datetime.now(tz.tzlocal())  # 2022-05-10 23:00:00
dt.tzname()                      # MSK

# А теперь попробуем поменять наш часовой пояс, но не меняя окружение
dt = datetime.now(tz.gettz(&amp;#x27;Europe/London&amp;#x27;))  # 2022-05-10 21:00:00
dt.tzname()                                   # BST (British Summer Time)

# Со временем по UTC всё ещё проще
datetime.now(tz.UTC)  # 2022-05-10 20:00:00

# А теперь решим простую задачу с приведением времени по МСК ко времени по UTC
dt = datetime.now(tz.tzlocal())  # 2022-05-10 23:30:00+03:00 MSK
dt.astimezone(tz.UTC)            # 2022-05-10 20:30:00+00:00 UTC&lt;/pre&gt;
  &lt;p id=&quot;VbeP&quot;&gt;Чуть выше мы рассматривали класс &lt;code&gt;timedelta&lt;/code&gt; из модуля &lt;strong&gt;&lt;em&gt;datetime&lt;/em&gt;&lt;/strong&gt;, который был невероятно полезен при выполнении арифметических операций над объектами даты и времени, но он мог показаться вам не слишком удобным. Класс &lt;code&gt;timedelta&lt;/code&gt; предоставлял нам возможность интерпретировать временной отрезок только как дни, секунды и микросекунды, что ограничивало удобство его применения. Разработчики &lt;em&gt;&lt;strong&gt;dateutil&lt;/strong&gt;&lt;/em&gt; создали расширенный класс представляющий более гибкий временной отрезок - &lt;code&gt;relativedelta&lt;/code&gt;. Давайте подробно посмотрим насколько удобно с ним работать:&lt;/p&gt;
  &lt;pre id=&quot;oNfm&quot; data-lang=&quot;python&quot;&gt;from datetime import datetime
from dateutil.relativedelta import relativedelta

# Попробуем прибавить к сегодняшней дате год
datetime.now() + relativedelta(days=365)   # 2023-05-10 00:00:00
datetime.now() + relativedelta(months=12)  # 2023-05-10 00:00:00
datetime.now() + relativedelta(years=1)    # 2023-05-10 00:00:00

# А теперь попробуем получить временной отрезок между сегодня и завтра
relativedelta(td, td + relativedelta(days=1))  # relativedelta(days=-1)

# Проробуем заодно узнать, какой сегодня день недели и когда будет понедельник
import calendar  # &amp;lt;- Это встроенный модуль для работы с Григорианским календарём

calendar.day_name[td.weekday()]              # Friday (Пятница)
td + relativedelta(weekday=calendar.MONDAY)  # 2022-05-16

# А что насчёт узнать какой день в году будет 260-м?
datetime(2003, 1, 1) + relativedelta(yearday=260)  # 2003-09-17
datetime(2000, 1, 1) + relativedelta(yearday=260)  # 2000-09-16
# 2000-й был високосным годом, поэтому дата отличается на 1 сутки
# попробуйте то же самое с 2004-м годом, он также был високосным&lt;/pre&gt;
  &lt;p id=&quot;iJBz&quot;&gt;Надеюсь пакет &lt;strong&gt;&lt;em&gt;dateutil&lt;/em&gt;&lt;/strong&gt; уже вселил в вас желание начать использовать его в своих проектах, но впереди ещё много всего интересного. Выше мы уже сталкивались с необходимостью парсить строки и превращать их в обьекты времени, но &lt;strong&gt;&lt;em&gt;dateutil&lt;/em&gt;&lt;/strong&gt; выводит парсинг на новый уровень:&lt;/p&gt;
  &lt;pre id=&quot;jOUO&quot; data-lang=&quot;python&quot;&gt;from dateutil import parser

parser.parse(&amp;#x27;18th November of 2000 at 12:35&amp;#x27;)  # 2000-11-18 12:35:00
parser.parse(&amp;#x27;11/18/2000 12:35:00&amp;#x27;)             # 2000-11-18 12:35:00
parser.parse(&amp;#x27;2000-11-18 12:35&amp;#x27;)                # 2000-11-18 12:35:00
# Вся магия происходит под капотом, просто наслаждайтесь&lt;/pre&gt;
  &lt;p id=&quot;bBjd&quot;&gt;Говоря о &lt;strong&gt;&lt;em&gt;dateutil&lt;/em&gt;&lt;/strong&gt; также нельзя не упомянуть модуль &lt;em&gt;rrule&lt;/em&gt; предоставляющий функционал “правил повторения”, описанных в &lt;a href=&quot;https://tools.ietf.org/html/rfc5545&quot; target=&quot;_blank&quot;&gt;iCalendar RFC&lt;/a&gt; (В спецификации это называется “Периодами времени”). Его удобно использовать, чтобы создать последовательность дат по определённым правилам, допустим: Все 31-е числа в году или дни с определённым интервалом:&lt;/p&gt;
  &lt;pre id=&quot;ktjI&quot; data-lang=&quot;python&quot;&gt;from datetime import datetime
from dateutil.rrule import rrule, YEARLY

# Попробуем узнать 5 високосных лет с 2000-го
start = datetime(2000, 2, 29)  # 29-е Февраля в 2000-м
list(rrule(freq=YEARLY, count=5, dtstart=start))
# [datetime.datetime(2000, 2, 29, 0, 0), 
#  datetime.datetime(2004, 2, 29, 0, 0),
#  datetime.datetime(2008, 2, 29, 0, 0), 
#  datetime.datetime(2012, 2, 29, 0, 0),
#  datetime.datetime(2016, 2, 29, 0, 0)]

# Можно получить даты между двумя другими датами
list(rrule(YEARLY, dtstart=parse(&amp;quot;19980101&amp;quot;), until=parse(&amp;quot;20220131&amp;quot;)))
# [datetime.datetime(1998, 1, 1, 0, 0),
#  ...
#  datetime.datetime(2022, 1, 1, 0, 0)]&lt;/pre&gt;
  &lt;p id=&quot;LmpL&quot;&gt;Увы, чтобы полностью раскрыть потенциал модуля rrule не хватит этой статьи, поэтому я рекомендую ознакомиться с &lt;a href=&quot;https://dateutil.readthedocs.io/en/stable/rrule.html&quot; target=&quot;_blank&quot;&gt;официальной документацией&lt;/a&gt;, если вас заинтересовала тема генерации периодов времени.&lt;/p&gt;
  &lt;h3 id=&quot;CdAE&quot;&gt;И про пасху не забудем&lt;/h3&gt;
  &lt;p id=&quot;PbVF&quot;&gt;Пусть вам кажется, что пасха не имеет никакого отношения к этой статье - это не так. Пасха является самым важным христианским праздником, но наш интерес вызывает не история праздника, не мифология и религия, а то, когда пасха празднуется. Википедия говорит следующее:&lt;/p&gt;
  &lt;blockquote id=&quot;PcU2&quot;&gt;Общее правило для расчёта даты Пасхи: «Пасха празднуется в первое воскресенье после весеннего полнолуния».&lt;br /&gt;Весеннее полнолуние — первое полнолуние, наступившее не ранее дня весеннего равноденствия.&lt;/blockquote&gt;
  &lt;p id=&quot;dTHc&quot;&gt;Попробуем вычислить пасху используя чистый Python:&lt;/p&gt;
  &lt;pre id=&quot;Hcn0&quot; data-lang=&quot;python&quot;&gt;def _easter(factor):
    &amp;quot;&amp;quot;&amp;quot; Метод вычисляющий месяц и день пасхи из фактора&amp;quot;&amp;quot;&amp;quot;
    d = 1 + (factor + 27 + (factor + 6) // 40) % 31
    m = 3 + (factor + 26) // 30
    return m, d

def easter(year = 2000):
    &amp;quot;&amp;quot;&amp;quot; Метод вычисляющий точную дату пасхи &amp;quot;&amp;quot;&amp;quot;
    g, e = year % 19, 10  # Лунный цикл в годах и разница календарей
    i = (19*g + 15) % 30  # Количество дней от 21-го марта до полнолуния
    j = (year + year//4 + i) % 7  # День недели полнолуния
    if year &amp;gt; 1600:
				# Получаем разницу между Юлианским и Григорианским календарём
        e = e + year // 100 - 16 - (year // 100 - 16) // 4
    m, d = _easter(i - j + e) # Точные месяц и день пасхи :)
    return datetime(year, int(m), int(d))

easter(2022)  # 2022-04-24 00:00:00&lt;/pre&gt;
  &lt;p id=&quot;F5VH&quot;&gt;Сам алгоритм вычисления пасхи является достаточно простым, но особенность этого замечательного праздника в том, что разные религии празднуют его в разные даты. Способ определения точной даты пасхи в разных типах христианства называется пасхалика, он определяет по какому календарю будет выбираться дата празднования.&lt;/p&gt;
  &lt;p id=&quot;EyXp&quot;&gt;Пакет &lt;em&gt;&lt;strong&gt;dateutil&lt;/strong&gt;&lt;/em&gt; предоставляет нам универсальную функцию вычисления пасхи с учётом всех особенностей этого праздника:&lt;/p&gt;
  &lt;pre id=&quot;ZIoD&quot; data-lang=&quot;python&quot;&gt;from dateutil.easter import easter  # &amp;lt;- Функция вычисления пасхи
from dateutil.easter import (
    EASTER_JULIAN,    # &amp;lt;- Алгоритм вычисления по Юлианскому календарю  
    EASTER_ORTHODOX,  # &amp;lt;- Алгоритм вычисления по Григорианскому календарю
    EASTER_WESTERN    # &amp;lt;- Более современный алгоритм
)

easter(EASTER_JULIAN)    # 2022-04-11
easter(EASTER_ORTHODOX)  # 2022-04-24
easter(EASTER_WESTERN)   # 2022-04-17&lt;/pre&gt;
  &lt;p id=&quot;Ihez&quot;&gt;Чтобы лишний раз убедиться в том, что дата вычисляется правильно, мы может воспользоваться сайтом &lt;a href=&quot;http://google.com/&quot; target=&quot;_blank&quot;&gt;google.com&lt;/a&gt; или встроенной в UNIX-подобные системы календарной утилитой:&lt;/p&gt;
  &lt;pre id=&quot;lZ2v&quot; data-lang=&quot;shell&quot;&gt;$ ncal -o
&amp;gt;&amp;gt; 24 апреля 2022&lt;/pre&gt;
  &lt;h2 id=&quot;6tjF&quot;&gt;Заключение&lt;/h2&gt;
  &lt;p id=&quot;sWyo&quot;&gt;Работа с датой и временем в мире современного IT всё ещё остаётся важной задачей, скрытой под множеством фреймворков и утилит, вычисляющих все производные за нас, но это всё ещё не значит, что мы не должны знать и понимать основ работы со временем.&lt;/p&gt;
  &lt;p id=&quot;vmLr&quot;&gt;Спасибо, что уделили время и прочли этот непростой материал. Я искренне надеюсь, что он улучшит опыт использования вашего программного обеспечения вашими пользователями. Заходите на чашечку чая в мой блог - &lt;a href=&quot;https://t.me/+B2irZkLDibZmODQy&quot; target=&quot;_blank&quot;&gt;Закладки разработчика&lt;/a&gt; 🔥.&lt;/p&gt;

</content></entry></feed>