8

我知道已经回答了与我类似的问题,但在阅读完这些问题后,我仍然没有我正在寻找的解决方案。

使用 Python 3.2.2,我需要将“月、日、年”与“月”作为字符串进行匹配,“日”是二月不超过 30、31 或 28 的两位数,闰年二月是不超过 29 的两位数。(基本上是真实有效的日期)

这是我到目前为止所拥有的:

pattern = "(January|February|March|April|May|June|July|August|September|October|November|December)[,][ ](0[1-9]|[12][0-9]|3[01])[,][ ]((19|20)[0-9][0-9])"
expression = re.compile(pattern)
matches = expression.findall(sampleTextFile)

我仍然不太熟悉正则表达式语法,所以我可能有一些不必要的字符(逗号和空格的 [,][ ] 感觉像是错误的处理方式),但是当我尝试匹配“ 1991 年 1 月 26 日”在我的示例文本文件中,“匹配”中的项目的打印结果是 ('January', '26', '1991', '19')。

为什么最后会出现额外的“19”?

另外,我可以在我的正则表达式中添加或更改哪些内容,以使我能够正确验证日期?我现在的计划是接受几乎所有的日期,然后通过将日期分组与月份和年份分组进行比较,以查看日期是否应该小于 31、30、29、28,然后使用高级构造将它们剔除

任何帮助将不胜感激,包括对我如何设计正则表达式的建设性批评。

4

6 回答 6

6

Here's one way to make a regular expression that will match any date of your desired format (though you could obviously tweak whether commas are optional, add month abbreviations, and so on):

years = r'((?:19|20)\d\d)'
pattern = r'(%%s) +(%%s), *%s' % years

thirties = pattern % (
     "September|April|June|November",
     r'0?[1-9]|[12]\d|30')

thirtyones = pattern % (
     "January|March|May|July|August|October|December",
     r'0?[1-9]|[12]\d|3[01]')

fours = '(?:%s)' % '|'.join('%02d' % x for x in range(4, 100, 4))

feb = r'(February) +(?:%s|%s)' % (
     r'(?:(0?[1-9]|1\d|2[0-8])), *%s' % years, # 1-28 any year
     r'(?:(29), *((?:(?:19|20)%s)|2000))' % fours)  # 29 leap years only

result = '|'.join('(?:%s)' % x for x in (thirties, thirtyones, feb))
r = re.compile(result)
print result

Then we have:

>>> r.match('January 30, 2001') is not None
True
>>> r.match('January 31, 2001') is not None
True
>>> r.match('January 32, 2001') is not None
False
>>> r.match('February 32, 2001') is not None
False
>>> r.match('February 29, 2001') is not None
False
>>> r.match('February 28, 2001') is not None
True
>>> r.match('February 29, 2000') is not None
True
>>> r.match('April 30, 1908') is not None
True
>>> r.match('April 31, 1908') is not None
False

And what is this glorious regexp, you may ask?

>>> print result
(?:(September|April|June|November) +(0?[1-9]|[12]\d|30), *((?:19|20)\d\d))|(?:(January|March|May|July|August|October|December) +(0?[1-9]|[12]\d|3[01]), *((?:19|20)\d\d))|(?:February +(?:(?:(0?[1-9]|1\d|2[0-8]), *((?:19|20)\d\d))|(?:(29), *((?:(?:19|20)(?:04|08|12|16|20|24|28|32|36|40|44|48|52|56|60|64|68|72|76|80|84|88|92|96))|2000))))

(I initially intended to do a tongue-in-cheek enumeration of the possible dates, but I basically ended up hand-writing that whole gross thing except for the multiples of four, anyway.)

于 2012-04-25T04:46:07.347 回答
2

一组由括号标识(...),它们从左到右计数,最外层在前。您的最终表达式如下所示:

((19|20)[0-9][0-9])

最外面的括号匹配整个年份,而里面的括号匹配前两位数。因此,对于像“1989”这样的日期,两个匹配组将是 1989 年和 19 年。由于您不想要内部组(前两位数),因此您应该改用非捕获组。非捕获组以 开头?:,使用如下:(?:a|b|c)

顺便说一句,这里有一些关于如何使用正则表达式的好文档。

于 2012-04-25T03:52:30.307 回答
2

以下是一些快速的想法:

每个建议你使用正则表达式以外的东西的人都在给你很好的建议。另一方面,学习更多关于正则表达式语法的信息总是一个好时机......

方括号中的表达式 -- [...]-- 匹配这些括号内的任何单个字符。因此[,],只包含一个字符的写作与写一个简单的无修饰逗号完全相同:,.

.findall方法返回字符串中所有匹配组的列表。一组由括号标识(...)————它们从左到右计数,最外层在前。您的最终表达式如下所示:

((19|20)[0-9][0-9])

最外面的括号匹配整个年份,而里面的括号匹配前两位数。因此,对于像“1989”这样的日期,最后两个匹配组将是198919

于 2012-04-25T03:41:53.433 回答
1

你有这个正则表达式:

pattern = "(January|February|March|April|May|June|July|August|September|October|November|December)[,][ ](0[1-9]|[12][0-9]|3[01])[,][ ]((19|20)[0-9][0-9])"

正则表达式的一个特征是“字符类”。方括号中的字符构成一个字符类。因此[,]是匹配单个字符,(逗号)的字符类。你不妨把逗号。

也许您想让逗号成为可选的?你可以通过在它后面加上一个问号来做到这一点: ,?

您放入括号中的任何内容都会构成“匹配组”。我认为神秘的额外“19”来自一个你不想拥有的匹配组。您可以使用以下语法创建一个不匹配的组:(?:

因此,例如:

r'(?:red|blue) socks'

这将匹配“red socks”或“blue socks”,但不构成匹配组。如果然后将其放在普通括号内:

r'((?:red|blue) socks)'

这将创建一个匹配组,其值为"red socks""blue socks"

我认为如果您将这些注释应用于您的正则表达式,它将起作用。现在大部分是正确的。

至于根据月份验证日期,这超出了正则表达式的范围。您的模式将匹配"February 31",并且没有简单的方法可以解决此问题。

于 2012-04-25T03:57:05.363 回答
1

Python 有一个日期解析器作为time模块的一部分:

import time
time.strptime("December 31, 2012", "%B %d, %Y")

如果日期格式始终相同,以上就是您所需要的。

因此,在实际生产代码中,我会编写一个解析日期的正则表达式,然后使用正则表达式的结果来构建一个格式始终相同的日期字符串。

既然您在评论中说这是作业,我将发布另一个答案,其中包含有关正则表达式的提示。

于 2012-04-25T03:41:13.393 回答
0

首先,正如其他人所说,我认为正则表达式不是解决这个问题的最佳选择,而是回答你的问题。通过使用括号,您将字符串分解为几个子组,当您调用函数 findall 时,您将创建一个包含您创建的所有匹配组和匹配字符串的列表。

((19|20)[0-9][0-9])

这是您的问题,正则表达式将匹配整个年份和 19 或 20,具体取决于年份是从 19 还是 20 开始。

于 2012-04-25T03:56:55.057 回答