从单个字符串中分离两个日期时间值

Separating two datetime values from a single string

我需要编写一个方法来接收包含两个日期时间值的字符串,并将这些值分开。这些日期时间值可以是任何有效的 ISO-8601 格式,这意味着我不能只根据字符索引进行拆分。这些值将用连字符分隔,这也意味着我不能只使用 str.split() 。

我已经使用一些 Reg-Ex 编写了此函数,但客户要求我改用 python-dateutil。

def split_range(times):
    regex = re.compile("[0-9]{4}-?[0-9]{2}-?[0-9]{2}([T]([0-9]{2}:?){2,3}(\.[0-9]{3})?)?Z?")
    split_times = regex.finditer(times)
    final_times = []

    for time in split_times:

        time = time.group(0)

        datetime_value = datetime.fromisoformat(time)
        final_times.append(datetime_value.isoformat())

    return final_times

此函数应接收如下字符串: (这些是我在测试中使用的所有字符串)

20080809-20080815

2008-08-08-2008-08-09

2008-08-08T17:21-2008-08-09T17:31

2008-08-08T17:21-2008-08-09T17:31

2008-08-08T17:21:000-2008-08-09T17:31:000

2008-08-08T17:21:000-2008-08-09T17:310:00

2008-08-08T17:21:000.000-2008-08-09T17:31:000.000

并将其拆分为两个单独的值

例如。 2019-08-08 & 2019-08-09

客户不太喜欢在这里使用正则表达式,希望我用 dateutil 代替它,但我还没有看到任何看起来可以满足我需要的东西。有没有我可以用来完成这个的 dateutil 方法,如果没有,是否有另一个库有一些东西?

使用re.findall()

import re

text = "2019-08-03-2019-08-09"
match = re.findall(r'\d{4}-\d{2}-\d{2}', text)

print (match)

输出:

['2019-08-03', '2019-08-09']

示例:

import re

text = "2019-08-03-2019-08-09xxxxxThis is test xxxxx---2017-01-01"
match = re.findall(r'\d{4}-\d{2}-\d{2}', text)

print (match)

输出:

['2019-08-03', '2019-08-09', '2017-01-01']

我认为最好的办法可能是要求您的客户将分隔符从 - 更改为其他内容,例如 space 或制表符或不会出现在ISO 8601 字符串并在其上拆分,但是如果您必须使用 - 作为分隔符 您必须支持任何有效的 ISO 8601 字符串,您最好的选择是尝试寻找模式 -(--|\d{4}),因为所有有效的 ISO 8601 日期时间要么以 4 位数字开头,要么以 -- 开头。如果您找到破折号后跟 4 位数字,则您找到了负时区或下一个 ISO 8601 日期时间的开头。

此外,没有包含 \d{4}-\d{4} 的有效 ISO 8601 日期时间格式,如果您找到表示时区偏移量的 -(\d{4}),则它必须位于 末尾 您的第一个 ISO 8601 字符串,因此使用否定先行足以确保模式不重复,因此,将它们放在一起:

import re
from dateutil.parser import isoparse


def parse_iso8601_pairs(isostr):
    # In a string containing two ISO 8601 strings delimited by -, the substring
    # "-\d{4}" is only found at the beginning of the second datetime or the
    # end of *either* datetime. If it is found at the end of the first datetime,
    # it will always be followed by `-\d{4}`, so we can use negative lookahead
    # to find the beginning of the next string.
    #
    # Note: ISO 8601 datetimes can also begin with `--`, but parsing these is
    # not supported yet in dateutil.parser.isoparse, as of verison 2.8.0. The
    # regex includes this type of string in order to make at least the splitting
    # method work even if the parsing method doesn't support "missing year"
    # ISO 8601 strings.
    m = re.search(r"-(--|\d{4})(?!-(--|\d{4}))", isostr)
    dt1 = None
    dt2 = None

    if m is None:
        raise ValueError(f"String does not contain two ISO 8601 datetimes " +
                         "delimited by -: {isostr}")

    split_on = m.span()[0]
    str1 = isostr[0:split_on]
    str2 = isostr[split_on + 1:]

    # You may want to wrap the error handling here with a nicer message
    dt1 = isoparse(str1)
    dt2 = isoparse(str2)

    return dt1, dt2

据我所知,这适用于由 - 分隔的任何符合 ISO 8601 的字符串 除了 晦涩的 "year missing" 格式:--MM-?DD。代码的拆分部分即使面对 --04-01 这样的字符串也能正常工作,但 dateutil.parser.isoparse 目前不支持该格式,因此解析会失败。也许更有问题的是 --MMDD 也是 一个有效的 ISO8601 格式,这将匹配 -\d{4} 并给出错误的拆分。如果你想支持那种格式并且你有一个可以处理 --MMDD 的修改过的解析器,我相信你可以制作一个更复杂的正则表达式来处理 --MMDD 的情况(如果有人想这样做我'我们会很乐意将其编辑到文章中),或者您可以简单地 "guess and check" 通过使用 re.finditer 遍历匹配,直到找到拆分字符串的位置,在字符串的两边产生有效的 ISO 8601 日期时间分隔符。

注意:如果用datetime.datetime.fromisoformat代替dateutil.parser.isoparse,此方法也有效。区别在于 datetime.datetime.fromisoformat 解析的字符串主要是 dateutil.parser.isoparse 处理的子集——它是 datetime.datetime.isoformatinverse 并且会解析任何可以通过在日期时间对象上调用 isoformat 方法来创建,而 isoparse 旨在解析任何有效的 ISO 8601 字符串。如果您知道日期时间是通过调用 isoformat() 方法生成的,那么 fromisoformat 是 ISO 8601 解析器的更好选择。