从单个字符串中分离两个日期时间值
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.isoformat
的 inverse 并且会解析任何可以通过在日期时间对象上调用 isoformat
方法来创建,而 isoparse
旨在解析任何有效的 ISO 8601 字符串。如果您知道日期时间是通过调用 isoformat()
方法生成的,那么 fromisoformat
是 ISO 8601 解析器的更好选择。
我需要编写一个方法来接收包含两个日期时间值的字符串,并将这些值分开。这些日期时间值可以是任何有效的 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.isoformat
的 inverse 并且会解析任何可以通过在日期时间对象上调用 isoformat
方法来创建,而 isoparse
旨在解析任何有效的 ISO 8601 字符串。如果您知道日期时间是通过调用 isoformat()
方法生成的,那么 fromisoformat
是 ISO 8601 解析器的更好选择。