strptime в Python: разбор строки в дату и время
Метод datetime.strptime() в Python превращает строку с датой, например "12.06.2024 18:40", в объект datetime по шаблону, который вы задаете кодами вроде %d, %m и %Y. Ниже разобрано, как составить шаблон под конкретную строку, какие коды работают при разборе, откуда берется ValueError, как читать смещение часового пояса и когда вместо strptime удобнее fromisoformat() или dateutil.

Содержание статьи
Синтаксис и первый пример
У метода два обязательных аргумента: строка и шаблон формата. Это метод класса, поэтому вызывается он у самого datetime, а не у готового объекта. Результат всегда объект datetime, даже если в строке нет времени: тогда часы, минуты и секунды будут нулевыми.
from datetime import datetime
raw = '12.06.2024 18:40'
dt = datetime.strptime(raw, '%d.%m.%Y %H:%M')
print(dt) # 2024-06-12 18:40:00
print(type(dt)) # <class 'datetime.datetime'>
print(dt.year, dt.month, dt.day) # 2024 6 12
Шаблон читается слева направо и должен описывать строку полностью. Обычные символы в шаблоне (точки, дефисы, двоеточия, пробелы, буква T) должны совпасть с символами строки, а каждый код с процентом забирает свой фрагмент: %d день, %m месяц, %Y четырехзначный год. Если нужна только дата без времени, возьмите dt.date(), если только время, то dt.time().
Название легко запомнить по последней букве: p означает parse, то есть разбор. Обратная операция, вывод даты в строку, делается методом strftime и его кодами форматирования. Коды у двух методов общие, но при разборе у некоторых из них свои особенности, о которых ниже.
Как подобрать шаблон под строку
Удобный прием: выпишите строку и замените в ней каждую часть даты на код, оставив разделители как есть. Из "2024-06-12" получится %Y-%m-%d, из "12/06/24 7:05 PM" получится %d/%m/%y %I:%M %p. Несколько типичных вариантов собрано в таблице.
| Строка | Шаблон | Комментарий |
|---|---|---|
| 12.06.2024 | %d.%m.%Y | привычная запись в России |
| 2024-06-12 18:40:05 | %Y-%m-%d %H:%M:%S | формат баз данных и логов |
| 06/12/2024 | %m/%d/%Y | американский порядок, месяц первым |
| 12 June 2024 | %d %B %Y | полное английское название месяца |
| Wed, 12 Jun 2024 | %a, %d %b %Y | сокращенные день недели и месяц |
| 20240612 | %Y%m%d | дата без разделителей |
| 2024-06-12T18:40:05.250+03:00 | %Y-%m-%dT%H:%M:%S.%f%z | ISO 8601 с миллисекундами и смещением |
Одна и та же строка может читаться по-разному. "03/04/2024" по шаблону %d/%m/%Y дает 3 апреля, а по шаблону %m/%d/%Y дает 4 марта, и ошибки не будет ни в одном случае. Поэтому формат надо брать из источника данных (документации API, настроек выгрузки), а не угадывать по одной строке.
from datetime import datetime
s = '03/04/2024'
print(datetime.strptime(s, '%d/%m/%Y').date()) # 2024-04-03
print(datetime.strptime(s, '%m/%d/%Y').date()) # 2024-03-04
Коды формата при разборе строки
Список директив тот же, что у strftime, но при чтении важно, сколько символов код готов принять и какие значения считаются допустимыми. В таблице только то, что чаще всего встречается в реальных строках.
| Код | Что ожидает в строке | Пример |
|---|---|---|
| %d | день месяца, одна или две цифры | 5, 05, 31 |
| %m | месяц числом, одна или две цифры | 6, 06, 12 |
| %Y | год четырьмя цифрами | 2024 |
| %y | год двумя цифрами | 24 |
| %H | час в 24-часовом формате | 0, 09, 23 |
| %I и %p | час в 12-часовом формате и AM/PM | 07 PM |
| %M, %S | минуты и секунды | 05, 59 |
| %f | дробная часть секунды, от 1 до 6 цифр | 250, 123456 |
| %b, %B | сокращенное и полное название месяца по локали | Jun, June |
| %a, %A | день недели по локали | Wed, Wednesday |
| %j | номер дня в году | 001, 164 |
| %z | смещение от UTC | +0300, +03:00, Z |
| %Z | имя пояса, только UTC, GMT и имена локального пояса | UTC |
| %% | сам символ процента | % |
Полный перечень с примечаниями есть в разделе strftime() and strptime() Behavior официальной документации. Ниже особенности, на которых чаще всего спотыкаются.
Числа без ведущего нуля
Коды %d, %m, %H при разборе принимают и "05", и "5", поэтому строка "1.2.2024" читается обычным шаблоном %d.%m.%Y. Варианты с дефисом вроде %-d, которые встречаются в статьях про вывод даты на Linux, strptime не понимает и падает с ошибкой '-' is a bad directive.
from datetime import datetime
print(datetime.strptime('1.2.2024', '%d.%m.%Y')) # 2024-02-01 00:00:00
# datetime.strptime('1.2.2024', '%-d.%m.%Y') -> ValueError
Двузначный год и 12-часовое время
Для %y действует правило POSIX: значения 69-99 превращаются в 1969-1999, а 00-68 в 2000-2068. Строка "05.03.69" станет 1969 годом, а "05.03.68" уже 2068. Если в данных встречаются даты рождения или архивные записи, лучше требовать четырехзначный год.
Код %p влияет на результат только в паре с %I. С %H он молча игнорируется, и "07:15 PM" превратится в 7 утра вместо 19 часов.
from datetime import datetime
print(datetime.strptime('07:15 PM', '%I:%M %p').time()) # 19:15:00
print(datetime.strptime('07:15 PM', '%H:%M %p').time()) # 07:15:00
Значения по умолчанию
Все, чего нет в строке, заполняется значениями по умолчанию: год 1900, месяц 1, день 1, время 00:00:00. Разбор одного времени "10:30" по шаблону %H:%M вернет 1900-01-01 10:30:00. Чтобы получить время без фиктивной даты, вызовите .time() у результата. Начиная с Python 3.13 разбор дня месяца без года (например, шаблон %d.%m) выдает DeprecationWarning: без года нельзя проверить, существует ли 29 февраля.
Ошибка ValueError и как ее обработать
Если строка не подходит под шаблон, strptime выбрасывает ValueError, и текст ошибки обычно прямо подсказывает причину. Чаще всего встречаются три сообщения.
time data '...' does not match format '...'- разделители или порядок частей в строке не совпадают с шаблоном, например в строке слеши, а в шаблоне точки.unconverted data remains: ...- начало строки разобрано, но в конце остались символы, которых нет в шаблоне. Типичный случай: в строке есть время, а шаблон описывает только дату, или в конце строки лишний пробел.day is out of range for month- формат верный, но такой даты не существует, например 31.04 или 29.02 в невисокосный год.
from datetime import datetime
for s, fmt in [('12/06/2024', '%d.%m.%Y'),
('2024-06-12 10:30', '%Y-%m-%d'),
('29.02.2023', '%d.%m.%Y')]:
try:
datetime.strptime(s, fmt)
except ValueError as e:
print(e)
# time data '12/06/2024' does not match format '%d.%m.%Y'
# unconverted data remains: 10:30
# day is out of range for month
Данные из форм, файлов и таблиц редко бывают однородными. Практичное решение: перед разбором убрать пробелы по краям через strip() и перебрать несколько допустимых шаблонов по очереди, возвращая None, если ни один не подошел.
from datetime import datetime
FORMATS = ('%d.%m.%Y', '%Y-%m-%d', '%d/%m/%Y', '%d.%m.%Y %H:%M')
def parse_date(text):
text = text.strip()
for fmt in FORMATS:
try:
return datetime.strptime(text, fmt)
except ValueError:
continue
return None
print(parse_date(' 2024-06-12 ')) # 2024-06-12 00:00:00
print(parse_date('12 июня')) # None
Порядок шаблонов в кортеже имеет значение: если строка подходит под два из них (как в примере с 03/04/2024), сработает первый.
Часовые пояса: %z и %Z
Без кода пояса strptime возвращает "наивный" datetime, у которого tzinfo равен None. Код %z читает смещение в видах +0300, +03:00 и букву Z для UTC, после чего объект становится "осведомленным": его можно сравнивать с другими осведомленными датами и переводить в нужный пояс.
from datetime import datetime, timezone
dt = datetime.strptime('2024-06-12 18:40:00+0300', '%Y-%m-%d %H:%M:%S%z')
print(dt.tzinfo) # UTC+03:00
print(dt.astimezone(timezone.utc)) # 2024-06-12 15:40:00+00:00
z = datetime.strptime('2024-06-12T15:40:00Z', '%Y-%m-%dT%H:%M:%S%z')
print(z == dt) # True
С %Z все скромнее: он принимает только "UTC", "GMT" и названия локального пояса системы, а результат остается наивным, поле tzinfo не заполняется. Строки вида "MSK" или "Europe/Moscow" так не разобрать. Если пояс известен заранее, разберите время без него и прикрепите пояс вручную через replace(tzinfo=...) с объектом ZoneInfo('Europe/Moscow') из модуля zoneinfo.
from datetime import datetime
from zoneinfo import ZoneInfo
naive = datetime.strptime('12.06.2024 18:40', '%d.%m.%Y %H:%M')
msk = naive.replace(tzinfo=ZoneInfo('Europe/Moscow'))
print(msk.isoformat()) # 2024-06-12T18:40:00+03:00
Сравнивать наивный и осведомленный объекты нельзя: проверка на больше-меньше и вычитание дают TypeError. Поэтому в одном наборе данных все даты должны быть либо с поясом, либо без него.
Русские названия месяцев
Коды %B и %b сверяются с названиями месяцев текущей локали. По умолчанию это английские имена, и строка "12 июня 2024" не разберется. Переключение через locale.setlocale(locale.LC_TIME, 'ru_RU.UTF-8') зависит от операционной системы: на одних машинах такой локали нет, на других названия хранятся в именительном падеже ("июнь"), а в тексте стоит родительный ("июня"). К тому же локаль меняется для всего процесса.
Надежнее заменить название месяца на номер перед разбором. Словарь по первым трем буквам покрывает и "июня", и "июнь", и "Июн".
from datetime import datetime
MONTHS = {'янв': 1, 'фев': 2, 'мар': 3, 'апр': 4, 'мая': 5, 'май': 5,
'июн': 6, 'июл': 7, 'авг': 8, 'сен': 9, 'окт': 10, 'ноя': 11, 'дек': 12}
def parse_ru(text):
day, month, year = text.lower().split()
num = MONTHS[month[:3]]
return datetime.strptime(f'{day}.{num}.{year}', '%d.%m.%Y')
print(parse_ru('12 июня 2024').date()) # 2024-06-12
print(parse_ru('1 Мая 2025').date()) # 2025-05-01
Альтернативы: fromisoformat, time.strptime, dateutil, pandas
strptime универсален, но не всегда лучший выбор. Если строка в стандарте ISO 8601, быстрее и проще вызвать fromisoformat(): шаблон не нужен, а с Python 3.11 метод понимает почти любые варианты стандарта, включая букву Z и дробные секунды. Для чистой даты есть date.fromisoformat().
from datetime import date, datetime
print(date.fromisoformat('2024-06-12')) # 2024-06-12
print(datetime.fromisoformat('2024-06-12T18:40:05+03:00'))
# 2024-06-12 18:40:05+03:00
Функция time.strptime() из модуля time принимает те же коды, но возвращает кортеж struct_time, а не datetime. Она пригодится в старом коде, построенном на этом модуле, в остальных случаях удобнее работать с datetime. В Python 3.14 у классов date и time из модуля datetime появились собственные методы strptime(), а в более ранних версиях дату или время получают через datetime.strptime(...).date() и .time().
Сторонняя библиотека python-dateutil (pip install python-dateutil) разбирает строку без шаблона и сама определяет формат. Параметр dayfirst=True подсказывает, что в неоднозначных строках день идет первым, как принято в России.
from dateutil import parser
print(parser.parse('12.06.2024 18:40', dayfirst=True)) # 2024-06-12 18:40:00
print(parser.parse('June 12, 2024 6:40 PM')) # 2024-06-12 18:40:00
Угадывание формата удобно для разовых задач, но в рабочем коде оно может тихо перепутать день и месяц, поэтому для известных форматов надежнее явный шаблон strptime. Когда дат тысячи и они лежат в таблице, используйте pd.to_datetime(df['col'], format='%d.%m.%Y') из библиотеки Pandas: она применяет шаблон ко всему столбцу сразу и работает заметно быстрее цикла.
| Способ | Нужен шаблон | Когда выбирать |
|---|---|---|
| datetime.strptime() | да | известный нестандартный формат |
| datetime.fromisoformat() | нет | строки ISO 8601 из API и баз данных |
| time.strptime() | да | код на модуле time, нужен struct_time |
| dateutil.parser.parse() | нет | разнородные строки, формат заранее не известен |
| pandas.to_datetime() | по желанию | целые столбцы таблиц |
Что делать с датой после разбора
Ради этого строку и превращают в объект: со строками "09.06.2024" и "12.05.2024" сравнение идет посимвольно и дает неверный порядок, а объекты datetime сравниваются по времени. Разность двух объектов дает интервал, подробно работа с ним описана в статье про timedelta и арифметику дат.
from datetime import datetime
dates = ['09.06.2024', '12.05.2024', '01.01.2025']
parsed = sorted(datetime.strptime(d, '%d.%m.%Y') for d in dates)
print([d.strftime('%d.%m.%Y') for d in parsed])
# ['12.05.2024', '09.06.2024', '01.01.2025']
delta = parsed[-1] - parsed[0]
print(delta.days) # 234
Обзор остальных возможностей модуля, от текущего момента до замены отдельных полей, есть в материале про модуль datetime.
Частые вопросы
Короткие ответы на вопросы, которые чаще всего возникают при разборе дат из строк.
Чем strptime отличается от strftime?
Направлением работы. strptime получает строку и шаблон и возвращает объект datetime, strftime вызывается у готового объекта и возвращает строку. Коды формата у них общие.
Как получить из строки date, а не datetime?
Разберите строку через datetime.strptime() и вызовите у результата .date(). Для строки вида 2024-06-12 проще сразу использовать date.fromisoformat().
Почему возникает ошибка unconverted data remains?
В конце строки остались символы, которые шаблон не описывает: время, миллисекунды, пояс или пробел. Дополните шаблон нужными кодами или обрежьте строку через strip() перед разбором.
Можно ли разобрать строку с миллисекундами?
Да, код %f принимает от одной до шести цифр после точки: "18:40:05.250" читается шаблоном %H:%M:%S.%f, а в объекте окажется 250000 микросекунд.
Учитывает ли strptime часовой пояс?
Только если в шаблоне есть %z: тогда объект получит смещение от UTC. Без него, а также с %Z, результат будет наивным, и пояс придется задать вручную через replace(tzinfo=...).