Книга: Простой Python. Современный стиль программирования. 2-е изд.
Назад: Глава 12. Обрабатываем данные
Дальше: Глава 14. Файлы и каталоги

Глава 13. Календари и часы

«Раз!» — ударили часы на колокольной башне,

Те, что шестьдесят минут назад

Пробили полночь.

Фредерик Б. Нидхэм. The Round of the Clock

Хоть я и снялась для календаря, я никогда не при­ходила вовремя.

Мэрилин Монро

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

Даты можно представить по-разному — способов не просто много, а слишком много. Даже англоговорящие люди, использующие римский календарь, имеют множество вариантов для написания простой даты:

July 21 1987;

• 21 Jul 1987;

• 21/7/1987;

7/21/1987.

Наряду с другими проблемами, представление даты может быть двусмысленным. В приведенных примерах довольно легко понять, что 7 означает месяц, а 21 — день месяца, хотя бы потому, что у месяца не может быть номера 21. Но что насчет даты 1/6/2012? Мы говорим о 6 января или о 1 июня?

Название месяца в римском календаре изменяется в зависимости от языка. Даже год и месяц могут иметь разные определения в разных культурах.

Время также способно доставить неприятности, особенно из-за часовых поя­сов и переходов на летнее время. Если вы взглянете на карту часовых поясов, то окажется, что эти пояса больше соответствуют политическим и историческим границам, а не сменяются каждые 15° (360°/24) долготы. Кроме того, разные страны переходят на летнее время и обратно в разные дни года. Страны Южного полушария переводят свои часы вперед, когда страны Северного полушария переводят их назад, и наоборот (если вы немного подумаете, то поймете, почему так происходит).

Стандартная библиотека Python имеет множество модулей для работы с датой и временем: datetime, time, calendar, dateutil и др. Их функции немного пересекаются друг с другом, что иногда приводит к путанице.

Високосный год

Високосные годы — еще одна проблема. Наверняка вы знаете, что каждый четвертый год — високосный (в такие годы проходят летние Олимпийские игры и выборы президента в США). Но знаете ли вы, что каждый сотый год не является високосным, а каждый 400-й — является? Рассмотрим пример кода, в котором проверяется, високосный год или нет:

>>> import calendar

>>> calendar.isleap(1900)

False

>>> calendar.isleap(1996)

True

>>> calendar.isleap(1999)

False

>>> calendar.isleap(2000)

True

>>> calendar.isleap(2002)

False

>>> calendar.isleap(2004)

True

Справка для любопытных:

год состоит из 365,242 196 дня (после одного оборота вокруг Солнца Земля на четверть оборота вокруг своей оси отстает от места, где начинала движение);

• каждые четыре года добавляется один день. Теперь в среднем каждый год состоит из 365,242 196 – 0,25 = 364,992 196 дня;

• каждые 100 лет один день вычитается. Теперь средний год состоит из 364,992 196 + + 0,01 = 365,002 196 дня;

каждые 400 лет добавляется один день. Теперь средний год состоит из 365,002 196 – – 0,0025 = 364,999 696 дня.

Получилось достаточно точно! О високосных секундах мы говорить не будем (/).

Модуль datetime

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

date — для годов, месяцев и дней;

• time — для часов, минут, секунд и долей секунды;

• datetime — для даты и времени одновременно;

timedelta — для интервалов даты и/или времени.

Вы можете создать объект date, указав год, месяц и день. Эти значения будут доступны как атрибуты:

>>> from datetime import date

>>> halloween = date(2019, 10, 31)

>>> halloween

datetime.date(2019, 10, 31)

>>> halloween.day

31

>>> halloween.month

10

>>> halloween.year

2019

Вы можете вывести на экран содержимое объекта date с помощью его метода isoformat():

>>> halloween.isoformat()

'2019-10-31'

iso в данном контексте ссылается на ISO 8601 — международный стандарт для представления даты и времени. Согласно этому стандарту мы начинаем записывать дату с самого общего элемента (год), а заканчиваем самым точным (день). Вследствие чего есть возможность корректно отсортировать даты: сначала по году, затем по месяцу и по дню. Я обычно выбираю этот формат для представления данных в программах и для имен файлов, которые сохраняют данные по дате. В следующем разделе будут показаны более сложные методы strptime() и strftime() для анализа и форматирования дат.

В этом примере используется метод today() для генерации сегодняшней даты:

>>> from datetime import date

>>> now = date.today()

>>> now

datetime.date(2019, 4, 5)

В следующем примере объект timedelta используется для того, чтобы добавить к объекту date некоторый временной интервал:

>>> from datetime import timedelta

>>> one_day = timedelta(days=1)

>>> tomorrow = now + one_day

>>> tomorrow

datetime.date(2019, 4, 6)

>>> now + 17*one_day

datetime.date(2019, 4, 22)

>>> yesterday = now — one_day

>>> yesterday

datetime.date(2019, 4, 4)

Объект date может иметь значение в диапазоне, начинающемся с date.min(year=1,month=1,day=1) и заканчивающемся date.max(year=9999,month=12,day=31). Таким образом, этот метод неприменим для исторических или астрономических расчетов.

Объект time модуля datetime применяется для представления времени дня:

>>> from datetime import time

>>> noon = time(12, 0, 0)

>>> noon

datetime.time(12, 0)

>>> noon.hour

12

>>> noon.minute

0

>>> noon.second

0

>>> noon.microsecond

0

Порядок аргументов таков: от самой крупной единицы времени (часа) до самой мелкой (микросекунды). Если вы передадите не все аргументы, объект time предположит, что остальные имеют значение 0. Кстати, то, что вы можете сохранять и получать микросекунды, не означает, что вы можете извлекать время из вашего компьютера с точностью до микросекунды. Высокая точность измерений зависит от многих факторов, присущих аппаратному обеспечению и операционной системе.

Объект datetime содержит дату и время дня. Вы можете создать такой объект напрямую, как показано в примере: 2 января, 2014, 3:04 утра, плюс 5 секунд и 6 микросекунд:

>>> from datetime import datetime

>>> some_day = datetime(2019, 1, 2, 3, 4, 5, 6)

>>> some_day

datetime.datetime(2019, 1, 2, 3, 4, 5, 6)

Объект datetime также имеет метод isoformat():

>>> some_day.isoformat()

'2019-01-02T03:04:05.000006'

Буква T, которая находится в середине, разделяет дату и время.

Объект datetime имеет метод now(), с помощью которого можно получить текущие дату и время:

>>> from datetime import datetime

>>> now = datetime.now()

>>> now

datetime.datetime(2019, 4, 5, 19, 53, 7, 580562)

>>> now.year

2019

>>> now.month

4

>>> now.day

5

>>> now.hour

19

>>> now.minute

53

>>> now.second

7

>>> now.microsecond

580562

Объединить объекты date и time в объект datetime можно с помощью метода combine():

>>> from datetime import datetime, time, date

>>> noon = time(12)

>>> this_day = date.today()

>>> noon_today = datetime.combine(this_day, noon)

>>> noon_today

datetime.datetime(2019, 4, 5, 12, 0)

Получить объекты date и time из объекта datetime можно с помощью методов date() и time():

>>> noon_today.date()

datetime.date(2019, 4, 5)

>>> noon_today.time()

datetime.time(12,    0)

Модуль time

В Python есть модуль datetime с объектом time, но есть и отдельный модуль time. Более того, в модуле time имеется функция с каким бы вы думали именем? Правильно, time().

Одним из способов представления абсолютного времени является подсчет количества секунд, прошедших с определенной начальной точки. В Unix считают количество секунд, прошедших с полуночи 1 января 1970 года: это значение часто называют epoch. Как правило, данный способ самый простой для обмена датой и временем между системами.

Функция time() модуля time возвращает текущее время как значение epoch:

>>> import time

>>> now = time.time()

>>> now

1554512132.778233

Если выполнить подсчеты, вы увидите, что прошло более миллиарда секунд после наступления нового, 1970 года. И куда ушло время?

Вы можете преобразовать значение epoch в строку с помощью функции ctime():

>>> time.ctime(now)

'Fri Apr  5 19:55:32 2019'

В следующем разделе вы увидите, как создавать более приятные глазу форматы для даты и времени.

Значения epoch полезны для обмена датой и временем с разными системами, например с JavaScript. Однако иногда бывает нужно получить значения именно дней, часов и т.д., которые объект time предоставляет как объекты struct_time. Функция localtime() предоставляет время в вашем текущем часовом поясе, а функция gmtime() — в UTC:

>>> time.localtime(now)

time.struct_time(tm_year=2019, tm_mon=4, tm_mday=5, tm_hour=19,

tm_min=55, tm_sec=32, tm_wday=4, tm_yday=95, tm_isdst=1)

>>> time.gmtime(now)

time.struct_time(tm_year=2019, tm_mon=4, tm_mday=6, tm_hour=0,

tm_min=55, tm_sec=32, tm_wday=5, tm_yday=96, tm_isdst=0)

В моем (Центральном) часовом поясе 19:55 — это то же самое, что 00:55 следующего дня в поясе UTC (раньше его называли Гринвичским временем или временем Зулу). Если вы опустите аргумент в функциях localtime() или gmtime(), они предположат, что сконвертировать нужно текущее время.

Некоторые значения tm_... из структуры struct_time выглядят неоднозначно, поэтому обратитесь к табл. 13.1 для получения более подробной информации.

Таблица 13.1. Значения структуры struct_time

Индекс

Имя

Значение

Диапазон

0

tm_year

Год

от 0000 до 9999

1

tm_mon

Месяц

от 1 до 12

2

tm_mday

День месяца

от 1 до 31

3

tm_hour

Часы

от 0 до 23

4

tm_min

Минуты

от 0 до 59

5

tm_sec

Секунды

от 0 до 61

6

tm_wday

День недели

от 0 (понедельник) до 6 (воскресенье)

7

tm_yday

День года

от 1 до 366

8

tm_isdst

Летнее время?

0 = нет, 1 = да, –1 = неизвестно

Если вы не хотите вводить все эти имена, начинающиеся с конструкции tm_, структура struct_time также может играть роль именованного кортежа (см. раздел «Именованные кортежи» на с. 219). Поэтому можно использовать индексы из предыдущей таблицы:

>>> import time

>>> now = time.localtime()

>>> now

time.struct_time(tm_year=2019, tm_mon=6, tm_mday=23, tm_hour=12,

tm_min=12, tm_sec=24, tm_wday=6, tm_yday=174, tm_isdst=1)

>>> now[0]

2019

print(list(now[x] for x in range(9)))

[2019, 6, 23, 12, 12, 24, 6, 174, 1]

Функция mktime() идет в другом направлении и преобразует объект struct_time в секунды epoch:

>>> tm = time.localtime(now)

>>> time.mktime(tm)

1554512132.0

Результат не совсем похож на предыдущее значение epoch, полученное с помощью функции now(), поскольку объект struct_time сохраняет время только до секунд.

109633.png

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

Еще один совет: никогда не используйте летнее время, если можно без этого обойтись. Если вы учитываете подобные переходы, в какой-то момент час у вас пропадет (весной), а в другой — наступит дважды (осенью). По разным причинам многие организации пользуются летним временем в своих компьютерных системах, а потом удивляются удвоению и потере данных из-за этого таинственного часа.

Читаем и записываем дату и время

Функция Isoformat() — это не единственный способ записывать дату и время. Вы уже видели функцию ctime() в модуле time, которую можете использовать для преобразования времени epoch в строку:

>>> import time

>>> now = time.time()

>>> time.ctime(now)

'Fri Apr  5 19:58:23 2019'

Вы также можете преобразовывать дату и время с помощью функции strftime(). Она предоставляется как метод в объектах datetime, date и time и как функция в модуле time. Функция strftime() использует для вывода информации на экран спецификаторы формата, которые вы можете увидеть в табл. 13.2.

Таблица 13.2. Спецификаторы вывода для strftime()

Спецификатор

Единица даты/времени

Диапазон

%Y

Год

1900–…

%m

Месяц

01–12

%B

Название месяца

Январь…

%b

Сокращение для месяца

Янв…

%d

День месяца

01–31

Название дня

Воскресенье…

А

Сокращение для дня

Вск…

Часы (24 часа)

00–23

%I

Часы (12 часов)

01–12

%p

AM или PM

AM, PM

%M

Минуты

00–59

%S

Секунды

00–59

Слева к числам добавляется ноль.

Рассмотрим пример работы функции strftime(), предоставленной модулем time. Она преобразует объект struct_time в строку. Сначала мы определим строку формата fmt, и потом снова будем ее использовать:

>>> import time

>>> fmt = "It's %A, %B %d, %Y, local time %I:%M:%S%p"

>>> t = time.localtime()

>>> t

time.struct_time(tm_year=2019, tm_mon=3, tm_mday=13, tm_hour=15,

tm_min=23, tm_sec=46, tm_wday=2, tm_yday=72, tm_isdst=1)

>>> time.strftime(fmt, t)

"It's Wednesday, March 13, 2019, local time 03:23:46PM"

Если мы попробуем сделать это с объектом date, функция отработает только для даты. Время будет установлено в полночь:

>>> from datetime import date

>>> some_day = date(2019, 7, 4)

>>> fmt = "It's %B %d, %Y, local time %I:%M:%S%p"

>>> some_day.strftime(fmt)

"It's Friday, July 04, 2019, local time 12:00:00AM"

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

>>> from datetime import time

>>> some_time = time(10, 35)

>>> some_time.strftime(fmt)

"It's Monday, January 01, 1900, local time 10:35:00AM"

Очевидно, вам не нужно использовать те части объекта time, которые касаются дней, поскольку они бессмысленны.

Чтобы пойти другим путем и преобразовать строку в дату или время, используйте функцию strptime() с такой же строкой формата. Эта строка работает не так, как регулярные выражения, — части строки, не касающиеся формата (без символа %), должны совпадать точно. Укажем формат год-месяц-день, например 2019-01-29.

Что произойдет, если строка даты, которую вы хотите проанализировать, имеет пробелы вместо дефисов?

>>> import time

>>> fmt = "%Y-%m-%d"

>>> time.strptime("2019 01 29", fmt)

Traceback (most recent call last):

  File "<stdin>",

    line 1, in <module>

  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/_strptime.py",

    line 494, in _strptime_time

    tt = _strptime(data_string, format)[0]

  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/_strptime.py",

    line 337, in _strptime(data_string, format))

ValueError: time data '2019 01 29' does not match format '%Y-%m-%d'

Будет ли довольна функция strptime(), если мы передадим ей несколько дефисов?

>>> time.strptime("2019-01-29", fmt)

time.struct_time(tm_year=2019, tm_mon=1, tm_mday=29, tm_hour=0, tm_min=0,

tm_sec=0, tm_wday=6, tm_yday=29, tm_isdst=-1)

Да.

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

>>> time.strptime("2019-13-29", fmt)

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/_strptime.py",

    line 494, in _strptime_time

    tt = _strptime(data_string, format)[0]

  File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/_strptime.py",

    line 337, in _strptime(data_string, format))

ValueError: time data '2019-13-29' does not match format '%Y-%m-%d'

Имена соответствуют вашей локали — региональному набору настроек операционной системы. Чтобы вывести на экран другие названия месяцев и дней, измените свою локаль с помощью функции setlocale(): ее первый аргумент — locale.LC_TIME для даты и времени, а второй аргумент — это строка, содержащая сокращенное обозначение языка и страны. Давайте пригласим на вечеринку в честь Дня Всех Святых наших иностранных друзей. Выведем на экран дату (месяц, число и день недели) на английском, французском, немецком, испанском и исландском. (А что? Думаете, исландцы не любят вечеринки? У них даже есть настоящие эльфы.)

>>> import locale

>>> from datetime import date

>>> halloween = date(2014, 10, 31)

>>> for lang_country in ['en_us', 'fr_fr', 'de_de', 'es_es', 'is_is',]:

...     locale.setlocale(locale.LC_TIME, lang_country)

...     halloween.strftime('%A, %B %d')

...

'en_us'

'Thursday, October 31'

'fr_fr'

'Jeudi, octobre 31'

'de_de'

'Donnerstag, Oktober 31'

'es_es'

'jueves, octubre 31'

'is_is'

'fimmtudagur, október 31'

>>>

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

>>> import locale

>>> names = locale.locale_alias.keys()

Из переменной names получим только имена локалей, которые, похоже, будут работать с методом setlocale(): например, те, которые мы использовали в предыдущем примере, — двухсимвольный код языка (), за которым следуют подчеркивание и двухсимвольный код страны ():

>>> good_names = [name for name in names if \

len(name) == 5 and name[2] == '_']

Как будут выглядеть первые пять из них?

>>> good_names[:5]

['sr_cs', 'de_at', 'nl_nl', 'es_ni', 'sp_yu']

Если вы хотите получить все локали для Германии, используйте следующий код:

>>> de = [name for name in good_names if name.startswith('de')]

>>> de

['de_at', 'de_de', 'de_ch', 'de_lu', 'de_be']

109674.png

Если вы запустите функцию set_locale() и получите ошибку locale.Error: unsupported locale, это будет означать, что установка данной локали не поддерживается вашей операционной системой. Вам потребуется выяснить, что нужно операционной системе для добавления локали. Ошибка может произойти даже в том случае, если Python с помощью функции locale.locale_alias.keys() сообщит вам, что эта локаль хорошая. Я получил ошибку при тестировании на macOS с использованием локали cy_gb (валлийский, Великобритания), даже несмотря на то, что локаль is_is (исландский, Исландия) перед этим была принята.

Все преобразования

На рис. 13.1, взятом из вики Python (), обобщены все стандартные преобразования времени, которые можно выполнить в Python.

113672.png 

Рис. 13.1. Преобразования даты и времени

Альтернативные модули

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

arrow (/). Этот модуль содержит множество функций для работы с датой и временем и имеет простой API;

• dateutil (). Модуль может проанализировать любой формат даты и хорошо работает с относительными датами и временем;

• iso8601 (). Этот модуль заполняет пробелы, связанные с работой модулей стандартной библиотеки, когда речь идет о формате ISO 8601;

• fleming (). Модуль содержит множество функций для работы с часовыми поясами;

• maya (). Интуитивный интерфейс для дат, времени и интервалов;

dateinfer (). Определяет правильные строки формата на основе строк, содержащих дату и время.

Читайте далее

Файлы и каталоги тоже хотят внимания.

Упражнения

13.1. Запишите текущие дату и время как строку в текстовый файл today.txt.

13.2. Прочтите текстовый файл today.txt и разместите данные в строке today_string.

13.3. Проанализируйте дату из строки today_string.

13.4. Создайте объект date с датой вашего рождения.

13.5. В какой день недели вы родились?

13.6. Когда вам будет (или уже было) 10 000 дней от роду?

Примерно в это время появилась система Unix, если не обращать внимания на досадные високосные секунды.

Назад: Глава 12. Обрабатываем данные
Дальше: Глава 14. Файлы и каталоги

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