R中日期的正则表达式

时间:2016-07-20 13:36:40

标签: regex r

我正在尝试在R中创建一个正则表达式,用于搜索某些文本中的日期。由于我无法控制实际的日期格式,我正试图抓住"所有可能的dd / mm / yy格式(一个或两个数字月,两个或四个数字年,可选的1或2位数日,带有一系列分隔符(" /"," - ","。"),可能包含空格。)

到目前为止我的正则表达式是:

pattern = "(\\d{0,2}[/\\.-])?[ ]?(\\d{1,2}[ ]*[/\\.-]|January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Febr|Mar|Apr|Jun|Jul|Aug|Sept|Sep|Oct|Nov|Dec|Jan\\.|Feb\\.|Febr\\.|Mar\\.|Apr\\.|Jun\\.|Jul\\.|Aug\\.|Sept\\.|Sep\\.|Oct\\.|Nov\\.|Dec\\.)[ ]*[']?\\d{2,4}"

这似乎适用于大多数格式,但它包含一个我很难理解的错误:

str_extract_all("09/11 /1985", pattern = pattern) # returns: "09/11 /1985"
str_extract_all(" 09/11 /1985", pattern = pattern) # returns: c("09/11",  "1985")

这听起来很奇怪。由于我不包括外观,因此开始时的额外空间应该没有区别。结果说不然。我做错了什么?

2 个答案:

答案 0 :(得分:2)

问题在于你的正则表达式的第一部分,你可能会尝试匹配日期:(\\d{0,2}[/\\.-])?[ ]?它可以选择匹配0到2天,然后是你的一个分隔符。然后它可以选择匹配一个空格。

如果09/11 /1985此部分与前导空格匹配,则将09与月份匹配,将11与年份匹配。

要消除此行为,您应该将空间移动到可选组中。您可能还希望匹配1或2位数字,否则它将匹配前导分隔符。

所以我会将第一部分重写为(\\d{1,2}[/\\.-][ ]?)?

还有一些其他方面可以改进,例如:

  • January|Jan|Jan\\.Jan(?:\\.|uary)?
  • 相同
  • 考虑使用非捕获组

答案 1 :(得分:1)

我认为最好的方法是在读取文件之前知道给定字符串中使用的日期格式,然后测试日期格式是否始终符合预期。但是,正如OP所说,事实并非如此。这里有一个不详尽的日期格式列表,但是它应该给你一个印象,找出一个只允许有效日期的正则表达式可能是一项繁琐的工作。此外,格式猜测可以使您的脚本在某些不能详细了解猜测完成的人身上有些不可预测。

如果您仍然认为您需要将regex用于不同的日期格式,请尝试以一种方式对其进行设计,使读者明白哪种格式具有优先权:

(?:format1)|(?:format2)|...|(?:formatN)

在这种情况下,format1优先于

https://stackoverflow.com/a/15504877/6018688上还有很好的正则表达式可以检查这些格式的日期有效性,甚至可以计算闰年dd/mm/yyyydd-mm-yyyydd.mm.yyyy

^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]))\1|(?:(?:29|30)(\/|-|\.)(?:0?[1,3-9]|1[0-2])\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)0?2\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9])|(?:1[0-2]))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$

并且从同一个问题中,一个与月份名称不同的答案:

^(?:(?:31(\/|-|\.)(?:0?[13578]|1[02]|(?:Jan|Mar|May|Jul|Aug|Oct|Dec)))\1|(?:(?:29|30)(\/|-|\.)(?:0?[1,3-9]|1[0-2]|(?:Jan|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec))\2))(?:(?:1[6-9]|[2-9]\d)?\d{2})$|^(?:29(\/|-|\.)(?:0?2|(?:Feb))\3(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00))))$|^(?:0?[1-9]|1\d|2[0-8])(\/|-|\.)(?:(?:0?[1-9]|(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep))|(?:1[0-2]|(?:Oct|Nov|Dec)))\4(?:(?:1[6-9]|[2-9]\d)?\d{2})$

我认为你现在得到了一个印象,写一个真正完成你想做的事情的正则表达是多么复杂。我真的会尽量将允许的日期保持在最低限度,并且目标是一个相当严格的正则表达式。在您的示例中,您只提供包含日期(和空格)的字符串,而不提供任何其他内容。如果情况也是如此,如果要在字符串使用"^yourregex$"的开头和结尾处允许空格,则应尝试使用"^\s*yourregex\s*$"对整个字符串进行数学计算。由于您在字符串的开头有一个带空格的示例,因此我使用后者进行进一步开发。

在你的情况下,我会从几年开始:

"^\\s*(?:\\d{4})\\s*$"

然后允许其他东西mm-dd-YY(不检查它确实是有效日期还是“33-13-2016”,但也允许2位数年份)

"(?:\\d{1,2}[/.-]\\d{1,2}[/.-](?:\\d{4}|\\d{2})"

如果你想在分隔符之间留出空格:

"(?:\\d{1,2}\\s*[/.-]\\s*\\d{1,2}\\s*[/.-]\\s*\\d{4})"

然后使用书面或缩写的月份名称格式化:

"(\\d{1,2}\\s*[/.-]?\\s*(?:January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Febr|Mar|Apr|Jun|Jul|Aug|Sept|Sep|Oct|Nov|Dec|Jan\\.|Feb\\.|Febr\\.|Mar\\.|Apr\\.|Jun\\.|Jul\\.|Aug\\.|Sept\\.|Sep\\.|Oct\\.|Nov\\.|Dec\\.)\\s*[/.-]?\\s*(?:'?\\d{2}|\\d{4}))"

放在一起:

"^\\s*(?:\\d{4}$)|(?:\\d{1,2}\\s*[/.-]\\s*\\d{1,2}\\s*[/.-]\\s*\\d{4})|(\\d{1,2}\\s*[/.-]?\\s*(?:January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Febr|Mar|Apr|Jun|Jul|Aug|Sept|Sep|Oct|Nov|Dec|Jan\\.|Feb\\.|Febr\\.|Mar\\.|Apr\\.|Jun\\.|Jul\\.|Aug\\.|Sept\\.|Sep\\.|Oct\\.|Nov\\.|Dec\\.)\\s*[/.-]?\\s*(?:'?\\d{2}|\\d{4}))\\s*$"

通过这种方式,您可以根据需要链接任意数量的格式。

请将以下正则表达式与您的正则表达式进行比较,以检查不同输入字符串的行为。我添加了单词边界\b约束,因为你使用了str_extract_all我假设在同一个字符串中可以有多个日期。

string = "only a year 1985. No space 2.Jan.2016. 2. Jan. 2016. 2. Jan. '16 2/1/16 02/01/2016 19855 ID1985A 2. Jan 2016   2.. Jan 2016 1January2016 2-Jan.-2016 2-Jan-2016 2.\tJan.\t2016"
pattern = "(\\d{1,2}[/\\.-][ ]?)?(\\d{1,2}[ ]*[/\\.-]|January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Febr|Mar|Apr|Jun|Jul|Aug|Sept|Sep|Oct|Nov|Dec|Jan\\.|Feb\\.|Febr\\.|Mar\\.|Apr\\.|Jun\\.|Jul\\.|Aug\\.|Sept\\.|Sep\\.|Oct\\.|Nov\\.|Dec\\.)[ ]*[']?\\d{2,4}"
p="\\s*(?:\\b\\d{4}\\b)|(?:\\b\\d{1,2}\\s*[/\\.-]\\s*\\d{1,2}\\s*[/\\.-]\\s*(?:\\d{4}|\\d{2})\\b)|\\b\\d{1,2}\\s*[/\\.-]?\\s*(?:January|February|March|April|May|June|July|August|September|October|November|December|(?:Jan|Feb|Febr|Mar|Apr|Jun|Jul|Aug|Sept|Sep|Oct|Nov|Dec).?)\\s*[/\\.-]?\\s*(?:\\d{4}|'?\\d{2})\\b\\s*"
str_extract_all(string, pattern=pattern)
str_extract_all(string, pattern=p)

警告:当允许使用空格的不同格式的多个版本时,您允许进行差异,这使得很难保证只匹配日期而不是文本中的其他数字值。

转义字符组中的点是不必要的,因为[\。]应该只是[。];除非你还想允许反斜杠作为day \ mont \ year之间的分隔符。 当输入格式是可变的时,空格也可以是标签\t,因此将[ ]替换为\s(与\n之类的行终止符匹配的任何空格字符)似乎是一个好主意。