Назад в будущее: практическое руководство по путешествию во времени с Python
В Python есть несколько встроенных библиотек для работы со временем и интервалами времени: time, datetime, calendar, timeit. Но когда какой модуль использовать? Рассмотрим на примерах.
Как в Python вывести строку, содержащую текущие дату и время? Как вычислить время, прошедшее между двумя днями? Как измерить длительность выполнения фрагмента кода? Как сгенерировать календарь на весь год или на один месяц? Как определить даты всех третьих четвергов одного года? На эти и другие вопросы вы получите ответы в данном руководстве.
Чтобы не обращаться далее к операции импорта, перечислим сразу все встроенные модули, рассматриваемые в этом руководстве:
1. Работа со шкалой времени: модуль time
1.1. Понятие epoch
Работа с модулем time в существенной мере зависит от используемой операционной системы. Время в библиотеке привязано к фиксированной начальной точке – эпохе (epoch). Узнаем эту начальную точку:
В Unix-системах точкой отсчета (epoch
) является 1 января 1970 г. Функция gmtime()
вернула объект именованного кортежа struct_time
.
С помощью функции time()
время, прошедшее с этой начальной точки, можно также вывести в секундах (seconds since the epoch
):
Так как точка epoch
для разных операционных систем может отличаться, число секунд, возвращаемое функцией time()
, также может быть различным.
Время до точки epoch
тоже существует, но значения секунд seconds since the epoch
отрицательны. Если мы передадим функции gmtime
отрицательное значение секунд, мы перенесемся в прошлое относительно момента времени epoch
:
1.2. Секунды, struct_time и преобразование друг в друга
Итак, модуль time
оперирует двумя основными типами объектов: struct_time
и секундами с начала эпохи. Для взаимных преобразований используются следующие функции:
gmtime()
: из секунд вstruct_time
для UTC.localtime()
: из секунд вstruct_time
для местного времени.calendar.timegm()
(не модуль time): изstruct_time
для UTC в секунды.mktime()
: изstruct_time
местного времени в секунды.
UTC – стандартное обозначение всемирного координированного времени без учета часовых поясов. Начало отсчета epoch
привязано к UTC, то есть не зависит от местного времени. Поэтому UTC удобнее использовать для общения с внешним миром.
В отсутствии аргумента функции gmtime()
и localtime()
возвращают значение для текущего времени – соответственно UTC и местное время.
Для преобразования объекта struct_time
в секунды можно или передать сам объект, или кортеж целых чисел. Порядок элементов в кортеже:
- Год
tm_year
- Месяц
tm_mon
– целое число (1
– Январь,12
– Декабрь) - День месяца
tm_day
- Час
tm_hour
– целое число в диапазоне от0
до23
- Минута
tm_min
- Секунда
tm_sec
- День недели
tm_wday
– целое число от0
(Понедельник) до6
(Воскресенье) - День года
tm_yday
- Целочисленный флаг
tm_isdst
для учета перехода на летнее время (daylight saving time, DST):1
– переход на летнее время учитывается,0
– не учитывается,-1
– неизвестно
Очевидно, что составлять такой кортеж вручную – задача неблагодарная, ведь нужно знать и день недели, и номер дня в году. Обычно используются «готовые» объекты (для «ручного» формирования дат удобнее применять описанный далее модуль datetime
):
Для перевода не местного, а международного времени в секунды необходимо использовать модуль calendar
:
Заметим, что, в отличие от представления в виде секунд, struct_time
не хранит составляющих времени, меньших, чем секунда.
Удобство использования struct_time
заключается в том, что это именованный кортеж. Значит, можно писать более ясный код: вместо индексов элементы объекта вызываются по ключам с говорящими именами:
Кроме вышеперечисленных параметров-меток, struct_time
содержит скрытые. Так, местным законодательством каждой страны регулируется переход на летнее время. Узнать, действует ли сейчас летнее время, можно следующим образом (в России в 2014 году произведен переход на постоянное «зимнее» время):
Считаем часовой пояс:
Смещение местного времени относительно UTC в секундах:
1.3. Строки временных меток
Распространенная задача – преобразование объектов указанных типов в строки вида timestamp
, например, Mon Dec 2 18:30:20 2019
. Для этого применяются функции ctime()
и asctime()
:
ctime()
– принимает время в секундахasctime()
– принимаетstruct_time
(по умолчанию используется местное время)
Хотя строковый вывод функций ctime()
и asctime()
довольно удобен, может потребоваться альтернативный формат. Для гибкого форматирования в библиотеку time
включена функция strftime()
. Функция принимает строку шаблона форматирования со спецификаторами и сам объект времени.
Функция strftime()
также удобна для автоматической локализации строк:
Список спецификаторов шаблона:
%a
,%A
– аббревиатура и полное название дня недели (Чт
,Четверг
)%b
,%B
– то же для месяца с учетом склонения (ноя
,ноября
)%с
– локализованная строка временной метки%d
– день месяца (28
)%H
,%I
– Час в 24- и 12-часовом представлении (17
,05
)%j
– номер дня года (в представлении от001
до366
)%m
– двузначное представление месяца (от01
до12
)%M
– двузначное представление минут (от00
до59
)%p
– местный эквивалент AM и PM%S
– двузначное представление секунд%W
– двузначное представление номера недели, первый день – Пн (%U
для Вс)%w
– двузначное представление номера дня недели%x
,%X
– принятый способ представления даты и времени.%y
,%Y
– двузначное (без века) и четырехзначное представление года%z
,%Z
– обозначение часового пояса в четырехзначном формате со знаком плюс или минус и в виде названия часового пояса
Пример одновременного использования нескольких спецификаторов:
Что, если у нас есть строка, содержащая метку времени, а мы хотим распарсить ее в объект struct_time
, чтобы обработать его в Python? Для этого есть функция strptime()
. Первый аргумент – строка, второй – правило, описанное через те же спецификаторы:
Функция strptime()
позволяет кратко задавать struct_time
, не используя все девять позиций кортежа. Неизвестные элементы вычисляются или на их место подставляются значения по умолчанию.
1.4. Приостановка выполнения кода и оценка производительности
Одна из наиболее часто используемых функций модуля time
– функция sleep()
, выполняющая задержку исполнения программного кода на переданное число секунд (можно использовать дробные значения):
Функция sleep()
нередко используется для тестирования кода, намеренного внесения задержек на различных этапах выполнения программы.
Для оценки производительности однократно запускаемых команд применяется функция perf_counter()
, обеспечивающая лучшее разрешение по времени на коротких интервалах:
В Python версии 3.7 добавлена функция perf_counter_ns()
– работает так же, но длительность выводится в наносекундах, что удобнее для совсем малых интервалов времени и быстро исполняемых команд.
Более удобные методы для измерения производительности фрагмента кода предоставляет модуль timeit
.
2. Оценка производительности: timeit
В момент запуска программы в фоновом режиме также запускается множество сторонних процессов. Модуль timeit за счет многократного запуска фрагмента нивелирует неоднородность длительности его выполнения.
У модуля timeit
есть интерфейс командной строки и интерфейс для вызова в коде. Во втором случае выводится время в секундах, которое длится общее количество запусков. Так как значение number
по умолчанию составляет 1 млн повторений, можно считать, что при дефолтном запуске выводится среднее время операции в микросекундах. При вызове timeit
в командной строке достаточное количество повторений определяется автоматически.
Сравним скорость выполнения операция конкатенации при использовании генератора и функции map()
:
Сравним с вызовом через интерпретатор Python:
Кроме куска кода, функции timeit()
можно передать строку setup
, однократно выполняемую перед началом повторения кода stmt
. В setup
, например, можно вынести импорт библиотек:
В блокнотах Jupyter команда timeit
относится к числу магических. С одним знаком процента она действует в пределах строки кода, с двумя – в границах ячейки:
Магические команды %time
и %%time
делают те же операции без многократного повторения. Это приводит к завышенным результатам, но позволяет быстрее получить оценку производительности:
3. Работа с датами: datetime
Вернемся к вопросу перемещения во времени. Модуль datetime поддерживает различные операции для работы с датами, например, определение интервала между двумя днями.
Структура представления времени в datetime
похожа на struct_time
в модуле time
:
Выведем отдельно дату и время:
Аналогично извлекаются год, месяц и т.д.:
Модуль datetime
также удобен для «ручного» задания дат и автоматизации арифметических операций с датами. Узнаем интервал времени между двумя главными датами сюжета фильма «Назад в будущее 2»:
Добавление найденной разности к первой дате «возвращает» нас в «будущее»:
Узнаем, какое число будет через четыре недели. Для форматирования строк в модуле datetime
имеется функция strftime()
с теми же спецификаторами, что и в модуле time
:
Если вам важнее оперировать не датами, а неделями, днями недели, месяцами, годами, то вам нужен модуль calendar
.
4. Работа с календарем: calendar
Модуль calendar содержит функции для работы с календарем. В частности, умеет генерировать строки и HTML для вывода каленадарей месяцев и годов. Для наглядности напечатаем календарь на декабрь 2019 года:
Всё хорошо, кроме того, что в заголовке используется шаблон названия месяцев в родительном падеже (так он обозначен в указанной выше локали системы). Мы можем вручную переобозначить константу именования месяцев:
Или использовать сокращения:
При помощи calendar
можно не только «рисовать» календари, но и осуществлять итерации по их составляющим.
В качестве примера рассмотрим следующую практическую задачу. Во многих музеях существует один день месяца, когда посещение музея для всех лиц или отдельных категорий граждан происходит без взимания платы. Например, в Эрмитаже это третий четверг месяца. Вычислим даты бесплатных дней посещения Эрмитажа на 2020 год:
5. Сторонние библиотеки
Конечно, даже в самом подробном руководстве не описать всего, что рассказано в документациях встроенных библиотек. Если вам не хватает их функционала, имеется ряд сторонних решений:
- dateutil – расширение стандартного модуля datetime для более специфичных операций, например, парсинга дат и их составляющих
- pytz – для сложных манипуляций с часовыми поясами и летним временем
- delorean – библиотека, названная в честь машины времени из фильма «Назад в будущее», упрощающая работу с датами
- arrow – библиотека, стремящаяся заменить собой все вышеперечисленные, объединив их лучшие качества и заполнив пробелы
- astropy – выполнение астрономических расчётов
- tqdm – создание текстовых и интерактивных виджетов, отображающих процент выполнения длительного процесса (в том числе в Jupyter)