Преобразование строк в дату со временем

Модуль datetime содержит в себе три объекта: date, time и datetime. Очевидно, что объект date хранит в себе дату, time — время, а datetime — дату и время.

Например, этот код выведет в консоль текущую дату и время: 

Хотите скачать книги по Python в 2 клика? Тогда вам в наш телеграм канал PythonBooks 
import datetime

print ('Текущая дата и время: {}'.format(datetime.datetime.now()))

После запуска этого кода в консоль выводится следующее: 

datetime-print-1.py
Текущая дата и время: 2018-06-29 08:15:27.243860

Без заданного форматирования используется формат строки по умолчанию — «2018-06-29 08:15:27.243860». Это стандарт ISO 8601 (YYYY-MM-DDTHH:MM:SS.mmmmmm). Если мы вводим строку в формате ISO 8601, то мы можем легко задать ее в качестве значения объекту datetime.

Взгляните на пример:

import datetime

date_time_str = '2018-06-29 08:15:27.243860'
date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S.%f')

print('Дата:', date_time_obj.date())
print('Время:', date_time_obj.time())
print('Дата и время:', date_time_obj)

После запуска кода в консоль выводится дата, время и дата со временем:

datetime-print-2.py
Дата: 2018-06-29
Время: 08:15:27.243860
Дата и время: 2018-06-29 08:15:27.243860

В этом примере мы используем метод strptime. Он принимает два аргумента: строковое представление строки и ее формат. Указание формата строки значительно ускоряет обработку данных: мы освобождаем метод datetime от самостоятельного анализа формата строки. Это довольно дорогая вычислительная операция. Тип возвращаемого значения — datetime

В нашем примере «2018-06-29 08:15:27.243860» — строка, а "%Y-%m-%d %H:%M:%S.%f" — ее формат. Возвращаемое значение datetime хранится в переменной date_time_obj. Так как datetime является объектом, мы можем вызывать методы date() и time() напрямую. Как видите, в консоль программа выводит дату и время прямо из введенной строки. 

Возможно, вам интересно, что значит формат "%Y-%m-%d %H:%M:%S.%f". Символы внутри — это токены. Каждый токен представляет собой часть даты и времени: день, месяц, год и т. д. Ознакомьтесь с документацией, если хотите узнать о видах форматирования, поддерживаемых Python. Краткая справка:

%Y: Год (4 цифры)
%m: Месяц
%d: День месяца
%H: Часы (24-часовой формат)
%M: Минуты
%S: Секунды
%f: Миллисекунды

Метод ожидает, что все токены будут дополнены нулями.

Итак, если известен формат строки, ее можно легко преобразовать в объект datetime с помощью strptime. Рассмотрим нетривиальный пример:

import datetime

date_time_str = 'Jun 28 2018 7:40AM'
date_time_obj = datetime.datetime.strptime(date_time_str, '%b %d %Y %I:%M%p')

print('Дата:', date_time_obj.date())
print('Время:', date_time_obj.time())
print('Дата и время:', date_time_obj)

Как видите, эта строка была обработана без проблем. Вывод следующий:

datetime-print-3.py
Date: 2018-06-28
Time: 07:40:00
Date-time: 2018-06-28 07:40:00

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

"Jun 28 2018 at 7:40AM" -> "%b %d %Y at %I:%M%p"
"September 18, 2017, 22:19:55" -> "%B %d, %Y, %H:%M:%S"
"Sun,05/12/99,12:30PM" -> "%a,%d/%m/%y,%I:%M%p"
"Mon, 21 March, 2015" -> "%a, %d %B, %Y"
"2018-03-12T10:12:45Z" -> "%Y-%m-%dT%H:%M:%SZ"
import datetime

date_time_str = '2018-06-29 08:15:27.243860'
date_time_obj = datetime.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S.%f')

print('Дата:', date_time_obj.date())
print('Время:', date_time_obj.time())
print('Дата и время:', date_time_obj)

Вы можете парсить строку любого формата, в этом вам поможет документация strptime, о которой мы говорили выше.

Работаем с часовыми поясами

Работу со временем и датой могут усложнить часовые пояса. Все примеры, которые мы обсуждали, — довольно примитивные объекты datetime. То есть, они не содержат данные о часовом поясе. Но у объекта datetime есть переменная, хранящая информацию о часовом поясе, — tzinfo.

import datetime as dt

dtime = dt.datetime.now()

print(dtime)
print(dtime.tzinfo)

Вывод:

datetime-tzinfo-1.py
2018-06-29 22:16:36.132767
None

Вывод tzinfo None — это примитивный datetime-объект. Для конвертирования часовых поясов в Python есть библиотека pytz. Как ее установить, описано здесь. Мы не будем подробно останавливаться на установке, а просто покажем, как использовать pytz для конвертирования времени в UTC.

import datetime as dt
import pytz

dtime = dt.datetime.now(pytz.utc)

print(dtime)
print(dtime.tzinfo)

Вывод:

datetime-tzinfo-2.py
2018-06-29 17:08:00.586525+00:00
UTC

+00:00 — разница между выводимым в консоль временем и UTC. В этом примере значение tzinfo — UTC, поэтому смещение 00:00. В данном случае объект datetime учитывает часовой пояс.

Точно так же мы можем преобразовать строки с датой и временем в любой другой часовой пояс. Например, мы можем преобразовать строку «2018-06-29 17:08:00.586525+00:00» в часовой пояс «Америка/Нью-Йорк»:

import datetime as dt
import pytz

date_time_str = '2018-06-29 17:08:00'
date_time_obj = dt.datetime.strptime(date_time_str, '%Y-%m-%d %H:%M:%S')

timezone = pytz.timezone('America/New_York')
timezone_date_time_obj = timezone.localize(date_time_obj)

print(timezone_date_time_obj)
print(timezone_date_time_obj.tzinfo)

Вывод:

datetime-tzinfo-3.py
2018-06-29 17:08:00-04:00
America/New_York

В начале программы мы конвертировали строку в datetime-объект, date_time_obj. Затем мы конвертировали его в datetime-объект с часовым поясом — timezone_date_time_obj. Поскольку мы поставили часовой пояс «Америка/Нью-Йорк», в выводе отобразится время, отстающее от UTC на 4 часа. Подробнее познакомиться с часовыми поясами можно здесь.

Изменение часовых поясов

Мы можем изменять часовые пояса datetime-объектов. Делается это следующим образом:

import datetime as dt
import pytz

timezone_nw = pytz.timezone('America/New_York')
nw_datetime_obj = dt.datetime.now(timezone_nw)

timezone_london = pytz.timezone('Europe/London')
london_datetime_obj = nw_datetime_obj.astimezone(timezone_london)


print('Америка/Нью-Йорк:', nw_datetime_obj)
print('Европа/Лондон:', london_datetime_obj)

В начале программы мы создали datetime-объект и установили часовой пояс «Америка/Нью-Йорк». После этого мы использовали метод astimezone(), чтобы поменять часовой пояс на «Европа/Лондон». Вывод этих двух объектов в консоли:

datetime-tzinfo-4.py
Америка/Нью-Йорк: 2018-06-29 22:21:41.349491-04:00
Европа/Лондон: 2018-06-30 03:21:41.349491+01:00

Как и ожидалось, время отличается: между часовыми поясами разница в пять часов.

Использование сторонних библиотек 

Модуль datetime может конвертировать любые строки в datetime-объекты. Но есть проблема: для этого необходимо написать подходящее форматирование, которое strptime сможет понять. На это уходит время, код становится сложнее читать. Для упрощения этого процесса можно использовать сторонние библиотеки.

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

Познакомимся с некоторыми из них.

dateutil

Модуль dateutil — расширение модуля datetime. Главное преимущество — не нужно писать код для парса строки. Пример:

from dateutil.parser import parse

datetime = parse('2018-06-29 22:21:41')

print(datetime)

Функция parse автоматически парсит строку и сохранит ее в переменной datetime. Парсинг происходит автоматически. То есть, отпадает нужда в форматировании. Попробуем спарсить разные типы строк с помощью dateutil:

from dateutil.parser import parse

date_array = [
    '2018-06-29 08:15:27.243860',
    'Jun 28 2018 7:40AM',
    'Jun 28 2018 at 7:40AM',
    'September 18, 2017, 22:19:55',
    'Sun, 05/12/1999, 12:30PM',
    'Mon, 21 March, 2015',
    '2018-03-12T10:12:45Z',
    '2018-06-29 17:08:00.586525+00:00',
    '2018-06-29 17:08:00.586525+05:00',
    'Tuesday , 6th September, 2017 at 4:30pm'
]

for date in date_array:
    print('Parsing: ' + date)
    dt = parse(date)
    print(dt.date())
    print(dt.time())
    print(dt.tzinfo)
    print('\n')

Вывод:

dateutil-1.py
Parsing: 2018-06-29 08:15:27.243860
2018-06-29
08:15:27.243860
None

Parsing: Jun 28 2018 7:40AM
2018-06-28
07:40:00
None

Parsing: Jun 28 2018 at 7:40AM
2018-06-28
07:40:00
None

Parsing: September 18, 2017, 22:19:55
2017-09-18
22:19:55
None

Parsing: Sun, 05/12/1999, 12:30PM
1999-05-12
12:30:00
None

Parsing: Mon, 21 March, 2015
2015-03-21
00:00:00
None

Parsing: 2018-03-12T10:12:45Z
2018-03-12
10:12:45
tzutc()

Parsing: 2018-06-29 17:08:00.586525+00:00
2018-06-29
17:08:00.586525
tzutc()

Parsing: 2018-06-29 17:08:00.586525+05:00
2018-06-29
17:08:00.586525
tzoffset(None, 18000)

Parsing: Tuesday , 6th September, 2017 at 4:30pm
2017-09-06
16:30:00
None

Как видите, с помощью модуля dateutil легко парсится практически любая строка.

Хоть это и удобно, но помните — прогнозирование формата замедляет выполнение кода. Если это критично — этот модуль не для вас.

Maya

Maya также может упростить парсинг строк и смену часовых поясов. Простые примеры: 

import maya

dt = maya.parse('2018-04-29T17:45:25Z').datetime()
print(dt.date())
print(dt.time())
print(dt.tzinfo)

Вывод:

maya-1.py
2018-04-29
17:45:25
UTC

Изменение часового пояса: 

import maya

dt = maya.parse('2018-04-29T17:45:25Z').datetime(to_timezone='America/New_York', naive=False)
print(dt.date())
print(dt.time())
print(dt.tzinfo)

Вывод:

maya-2.py
2018-04-29
13:45:25
America/New_York

Просто, не так ли? Опробуем maya с тем же списком строк, который был в случае с dateutil:

import maya

date_array = [
    '2018-06-29 08:15:27.243860',
    'Jun 28 2018 7:40AM',
    'Jun 28 2018 at 7:40AM',
    'September 18, 2017, 22:19:55',
    'Sun, 05/12/1999, 12:30PM',
    'Mon, 21 March, 2015',
    '2018-03-12T10:12:45Z',
    '2018-06-29 17:08:00.586525+00:00',
    '2018-06-29 17:08:00.586525+05:00',
    'Tuesday , 6th September, 2017 at 4:30pm'
]

for date in date_array:
    print('Parsing: ' + date)
    dt = maya.parse(date).datetime()
    print(dt)
    print(dt.date())
    print(dt.time())
    print(dt.tzinfo)

Вывод:

maya-3.py
Parsing: 2018-06-29 08:15:27.243860
2018-06-29 08:15:27.243860+00:00
2018-06-29
08:15:27.243860
UTC

Parsing: Jun 28 2018 7:40AM
2018-06-28 07:40:00+00:00
2018-06-28
07:40:00
UTC

Parsing: Jun 28 2018 at 7:40AM
2018-06-28 07:40:00+00:00
2018-06-28
07:40:00
UTC

Parsing: September 18, 2017, 22:19:55
2017-09-18 22:19:55+00:00
2017-09-18
22:19:55
UTC

Parsing: Sun, 05/12/1999, 12:30PM
1999-05-12 12:30:00+00:00
1999-05-12
12:30:00
UTC

Parsing: Mon, 21 March, 2015
2015-03-21 00:00:00+00:00
2015-03-21
00:00:00
UTC

Parsing: 2018-03-12T10:12:45Z
2018-03-12 10:12:45+00:00
2018-03-12
10:12:45
UTC

Parsing: 2018-06-29 17:08:00.586525+00:00
2018-06-29 17:08:00.586525+00:00
2018-06-29
17:08:00.586525
UTC

Parsing: 2018-06-29 17:08:00.586525+05:00
2018-06-29 12:08:00.586525+00:00
2018-06-29
12:08:00.586525
UTC

Parsing: Tuesday , 6th September, 2017 at 4:30pm
2017-09-06 16:30:00+00:00
2017-09-06
16:30:00
UTC

Как видите, все форматы даты успешно пропарсены. 

Но заметили ли вы разницу? Если мы не указываем часовой пояс, он автоматически конвертируется в UTC. Именно поэтому важно запомнить, что нужно указать параметры to_timezone и naive, если время не в UTC.

Arrow

Arrow — еще одна библиотека для работы с датой и временем. Как и maya, эта библиотека определяет формат даты и времени. После выполнения кода возвращается datetime-объект из объекта arrow.

Попробуем спарсить ту же строку, которая была в примере с maya:

import arrow

dt = arrow.get('2018-04-29T17:45:25Z')
print(dt.date())
print(dt.time())
print(dt.tzinfo)

Вывод:

arrow-1.py
2018-04-29
17:45:25
tzutc()

Изменить часовой пояс можно с помощью метода to:

import arrow

dt = arrow.get('2018-04-29T17:45:25Z').to('America/New_York')
print(dt)
print(dt.date())
print(dt.time())

Вывод:

arrow-2.py
2018-04-29T13:45:25-04:00
2018-04-29
13:45:25

Как видите, строка с датой и временем конвертировалась в регион «Америка/Нью-Йорк». 

Попробуем спарсить список строк из примеров выше: 

import arrow

date_array = [
    '2018-06-29 08:15:27.243860',
    #'Jun 28 2018 7:40AM',
    #'Jun 28 2018 at 7:40AM',
    #'September 18, 2017, 22:19:55',
    #'Sun, 05/12/1999, 12:30PM',
    #'Mon, 21 March, 2015',
    '2018-03-12T10:12:45Z',
    '2018-06-29 17:08:00.586525+00:00',
    '2018-06-29 17:08:00.586525+05:00',
    #'Tuesday , 6th September, 2017 at 4:30pm'
]

for date in date_array:
    dt = arrow.get(date)
    print('Parsing: ' + date)
    print(dt)
    print(dt.date())
    print(dt.time())
    print(dt.tzinfo)

Половина строк, которые закомментированы, вызовут ошибку. Вывод остальных: 

arrow-3.py
Parsing: 2018-06-29 08:15:27.243860
2018-06-29T08:15:27.243860+00:00
2018-06-29
08:15:27.243860
tzutc()

Parsing: 2018-03-12T10:12:45Z
2018-03-12T10:12:45+00:00
2018-03-12
10:12:45
tzutc()

Parsing: 2018-06-29 17:08:00.586525+00:00
2018-06-29T17:08:00.586525+00:00
2018-06-29
17:08:00.586525
tzoffset(None, 0)

Parsing: 2018-06-29 17:08:00.586525+05:00
2018-06-29T17:08:00.586525+05:00
2018-06-29
17:08:00.586525
tzoffset(None, 18000)

Для того чтобы спарсить закомментированные строки, необходимо указать токены. Они должны помочь библиотеке понять, как правильно их парсить. Например, «MMM» — для названий месяца воде «Jan, Feb, Mar». Документацию, в которой подробно рассказывается обо всех токенах, можно прочесть здесь.