Проверить строку даты-времени ISO-8601 в Python?

Я хочу написать функцию, которая принимает строку и возвращает True если это действительная дата-время ISO-8601 - с точностью до микросекунд, включая смещение часового пояса--False иначе.

Я нашел другие вопросы, которые обеспечивают различные способы анализа строк даты и времени, но я хочу вернуться True только в случае формата ISO-8601. Разбор мне не поможет, если я не смогу выдать ошибку для форматов, которые не соответствуют ISO-8601.

(Я использую красивую библиотеку стрелок в другом месте моего кода. Решение, которое использует arrow будет приветствоваться.)


РЕДАКТИРОВАТЬ: Похоже, что общего решения "является ли эта строка допустимым дата-время ISO 8601" не существует среди распространенных пакетов даты-времени Python.

Итак, чтобы сделать этот вопрос более узким, конкретным и ответственным, я остановлюсь на строке формата, которая проверит строку даты и времени в этой форме:

'2016-12-13T21:20:37.593194+00:00'

В настоящее время я использую:

format_string = '%Y-%m-%dT%H:%M:%S.%f%z'
datetime.datetime.strptime(my_timestamp, format_string)

Это дает:

ValueError: time data '2016-12-13T21:20:37.593194+00:00' does not match format '%Y-%m-%dT%H:%M:%S.%f%z'

Проблема, кажется, лежит с двоеточием в смещении UTC (+00:00). Если я использую смещение без двоеточия (например, '2016-12-13T21:20:37.593194+0000'), это анализирует должным образом, как и ожидалось. Это видимо потому что datetime"s %z токен не относится к форме смещения UTC, в которой есть двоеточие, только к форме без, даже если оба действительны в соответствии со спецификацией.

3 ответа

Последние версии Python (начиная с 3.7) имеют fromisoformat() функция в datetimeстандартная библиотека. См. https://docs.python.org/3.7/library/datetime.html

Итак, это поможет:

from datetime import datetime

def datetime_valid(dt_str):
    try:
        datetime.fromisoformat(dt_str)
    except:
        return False
    return True

Обновить:

Я узнал, что Python не распознает суффикс Z как действительный. Поскольку я хотел поддержать это в своем API, теперь я использую:

from datetime import datetime

def datetime_valid(dt_str):
    try:
        datetime.fromisoformat(dt_str)
    except:
        try:
            datetime.fromisoformat(dt_str.replace('Z', '+00:00'))
        except:
            return False
        return True
    return True

https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch04s07.html

предоставить множество вариантов для проверки даты и времени в формате ISO8601 (например, 2008-08-30T01:45:36 или 2008-08-30T01:45:36.123Z). Регулярное выражение для типа XML-схемы dateTime имеет вид:

>>> regex = r'^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(\.[0-9]+)?(Z|[+-](?:2[0-3]|[01][0-9]):[0-5][0-9])?$'

Таким образом, для проверки вы можете сделать:

>>> import re
>>> match_iso8601 = re.compile(regex).match
>>> def validate_iso8601(str_val):
...     try:            
...         if match_iso8601( str_val ) is not None:
...             return True
...     except:
...         pass
...     return False

Некоторые примеры:

>>> validate_iso8601('2017-01-01')
False

>>> validate_iso8601('2008-08-30T01:45:36.123Z')
True

>>> validate_iso8601('2016-12-13T21:20:37.593194+00:00')
True

Вот грубое, но функциональное решение (для более узкого вопроса), использующее datetime.strptime():

import datetime

def is_expected_datetime_format(timestamp):
    format_string = '%Y-%m-%dT%H:%M:%S.%f%z'
    try:
        colon = timestamp[-3]
        if not colon == ':':
            raise ValueError()
        colonless_timestamp = timestamp[:-3] + timestamp[-2:]
        datetime.datetime.strptime(colonless_timestamp, format_string)
        return True
    except ValueError:
        return False

Учитывая ограничения, которые вы наложили на проблему, вы можете легко решить ее с помощью регулярного выражения.

>>> import re
>>> re.match(r'^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{6}[+-]\d\d:\d\d$', '2016-12-13T21:20:37.593194+00:00')
<_sre.SRE_Match object; span=(0, 32), match='2016-12-13T21:20:37.593194+00:00'>

Если вам нужно передать все варианты ISO 8601, это будет гораздо более сложное регулярное выражение, но это все же можно сделать. Если вам также необходимо проверить числовые диапазоны, например, проверить, что час находится в диапазоне от 0 до 23, вы можете поместить круглые скобки в регулярное выражение, чтобы создать группы совпадений, а затем проверить каждую группу.

In [1]  import dateutil.parser as dp

In [2]: import re
     ...: def validate_iso8601_us(str_val):
     ...:     try:
     ...:         dp.parse(str_val)
     ...:         if re.search('\.\d\d\d\d\d\d',str_val):
     ...:             return True
     ...:     except:
     ...:         pass
     ...:     return False
     ...:

In [3]: validate_iso8601_us('2019/08/15T16:03:5.12345')
Out[3]: False

In [4]: validate_iso8601_us('2019/08/15T16:03:5.123456')
Out[4]: True

In [5]: validate_iso8601_us('2019/08/15T16:03:5.123456+4')
Out[5]: True

In [6]: validate_iso8601_us('woof2019/08/15T16:03:5.123456+4')
Out[6]: False
Другие вопросы по тегам