如何在Python中解析多种(未知)日期格式?

17

我有一堆Excel文档,需要从中提取日期。我想把它们转换为标准格式,以便将其放入数据库。是否有一个函数可以接受这些字符串并返回标准格式?以下是我的一小部分数据:

好消息是我知道日期始终是月/日。

10/02/09
07/22/09
09-08-2008
9/9/2008
11/4/2010
 03-07-2009
09/01/2010

我想把它们都转换成 MM/DD/YYYY 的格式。有没有一种方法可以不用逐个模式匹配字符串就能实现这个目的?


日期是否总是在2000年之后,如果不是,那么20世纪和21世纪之间的分界线应该在哪里? - Tim Pietzcker
是的,日期始终在2000年之后。 - Andy
4个回答

26

第三方模块dateutil有一个名为parse的函数,其操作方式类似于PHP的strtotime函数:你不需要指定特定的日期格式,它会尝试一系列自己的解析规则。

>>> from dateutil.parser import parse
>>> parse("10/02/09", fuzzy=True)
datetime.datetime(2009, 10, 2, 0, 0)  # default to be in American date format

它还允许您指定不同的假设:

  • dayfirst - 是否将模糊的三个整数日期(例如01/05/09)中的第一个值解释为日(True)或月(False)。 如果yearfirst设置为True,则区分YDM和YMD。如果设置为None,则此值从当前parserinfo对象检索(该对象本身默认为False)。
  • yearfirst - 是否将模糊的三个整数日期(例如01/05/09)中的第一个值解释为年份。 如果为True,则将第一个数字视为年份,否则将最后一个数字视为年份。 如果将其设置为None,则该值从当前parserinfo对象检索(该对象本身默认为False)。

dateutilparse 函数能否检测字符串中的日期,还是需要接收一个日期作为参数? - eyquem
parse('2023年12月4日10点',fuzzy=True)会得到错误的结果datetime.datetime(2010, 12, 4, 0, 0) - undefined

16
import re

ss = '''10/02/09
07/22/09
09-08-2008
9/9/2008
11/4/2010
03-07-2009
09/01/2010'''


regx = re.compile('[-/]')
for xd in ss.splitlines():
    m,d,y = regx.split(xd)
    print xd,'   ','/'.join((m.zfill(2),d.zfill(2),'20'+y.zfill(2) if len(y)==2 else y))

结果

10/02/09     10/02/2009
07/22/09     07/22/2009
09-08-2008     09/08/2008
9/9/2008     09/09/2008
11/4/2010     11/04/2010
03-07-2009     03/07/2009
09/01/2010     09/01/2010

编辑1

而且编辑2: 根据JBernardo提供的信息,我添加了第4种解决方案,看起来是最快的'{0:0>2}'.format(day)

import re
from time import clock
iterat = 100

from datetime import datetime
dates = ['10/02/09', '07/22/09', '09-08-2008', '9/9/2008', '11/4/2010',
         ' 03-07-2009', '09/01/2010']

reobj = re.compile(
r"""\s*  # optional whitespace
(\d+)    # Month
[-/]     # separator
(\d+)    # Day
[-/]     # separator
(?:20)?  # century (optional)
(\d+)    # years (YY)
\s*      # optional whitespace""",
re.VERBOSE)

te = clock()
for i in xrange(iterat):
    ndates = (reobj.sub(r"\1/\2/20\3", date) for date in dates)
    fdates1 = [datetime.strftime(datetime.strptime(date,"%m/%d/%Y"), "%m/%d/%Y")
               for date in ndates]
print "Tim's method   ",clock()-te,'seconds'



regx = re.compile('[-/]')


te = clock()
for i in xrange(iterat):
    ndates = (reobj.match(date).groups() for date in dates)
    fdates2 = ['%s/%s/20%s' % tuple(x.zfill(2) for x in tu) for tu in ndates]
print "mixing solution",clock()-te,'seconds'


te = clock()
for i in xrange(iterat):
    ndates = (regx.split(date.strip()) for date in dates)
    fdates3 = ['/'.join((m.zfill(2),d.zfill(2),('20'+y.zfill(2) if len(y)==2 else y)))
              for m,d,y in ndates]
print "eyquem's method",clock()-te,'seconds'



te = clock()
for i in xrange(iterat):
    fdates4 = ['{:0>2}/{:0>2}/20{}'.format(*reobj.match(date).groups()) for date in dates]
print "Tim + format   ",clock()-te,'seconds'


print fdates1==fdates2==fdates3==fdates4

结果

number of iteration's turns : 100
Tim's method    0.295053700959 seconds
mixing solution 0.0459111423379 seconds
eyquem's method 0.0192239516475 seconds
Tim + format    0.0153756971906 seconds 
True

混合解决方案很有趣,因为它结合了我的解决方案的速度和 Tim Pietzcker 的正则表达式检测字符串中日期的能力。

对于结合 Tim 的解决方案和使用 {:0>2} 进行格式化更为真实。我不能将{:0>2}与我的解决方案相结合,因为 regx.split(date.strip()) 生成带有 2 或 4 个数字的年份。


我已经在你的第一个答案上投了赞成票,但是考虑到性能改进和测试,我会再点一次+1。 - Tim Pietzcker

10

如果你不想安装像dateutil这样的第三方模块:

import re
from datetime import datetime
dates = ['10/02/09', '07/22/09', '09-08-2008', '9/9/2008', '11/4/2010', ' 03-07-2009', '09/01/2010']
reobj = re.compile(
    r"""\s*  # optional whitespace
    (\d+)    # Month
    [-/]     # separator
    (\d+)    # Day
    [-/]     # separator
    (?:20)?  # century (optional)
    (\d+)    # years (YY)
    \s*      # optional whitespace""", 
    re.VERBOSE)
ndates = [reobj.sub(r"\1/\2/20\3", date) for date in dates]
fdates = [datetime.strftime(datetime.strptime(date,"%m/%d/%Y"), "%m/%d/%Y")
          for date in ndates]

结果:

['10/02/2009', '07/22/2009', '09/08/2008', '09/09/2008', '11/04/2010', '03/07/2009', '09/01/2010']

你好,@Tim Pietzcker先生 - strptime 是一个非常慢的函数。请参阅我的回答编辑 - 使用日期作为 datetime.date 类以外的其他对象并不是很好的选择,因为它会覆盖 datetime.date。虽然在您的代码中这种情况并未发生,但它对包含您代码片段的代码来说是有风险的。 - 最好将 ndates 设为生成器。 - eyquem
@Tim Pietzcker的Tim's + format解决方案比你的纯Tim's解决方案(请参见我的答案中的编辑)更短,更清晰,更快。因此...尽管您的解决方案得到了很多赞,但并不是最好的,抱歉。 - eyquem

4
您可以使用类似于r'(\d+)\D(\d+)\D(\d+)'的正则表达式与re.findall函数一起获取以元组形式表示的月、日和年。
然后只需将两位数年份与数字2019连接起来,并使用您想要的分隔符将它们拼接回去: '/'.join(the_list) 如Tim所指出的:
为了规范日期,只需执行'{0:0>2}'.format(day)以及相同操作处理月份即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接