【问题标题】:How can I parse multiple (unknown) date formats in python?如何在 python 中解析多个(未知)日期格式?
【发布时间】:2011-08-13 05:43:30
【问题描述】:

我有一堆要从中提取日期的 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 年以上,如果不是,那么 1900 年代和 2000 年代之间的分界线应该在哪里?
  • 是的,日期总是在 2000 年之后

标签: python parsing date


【解决方案1】:

第三方模块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 – 是否将不明确的 3 整数日期(例如 01/05/09)中的第一个值解释为日(True)或月(False)。如果 yearfirst 设置为 True,这将区分 YDM 和 YMD。如果设置为 None,则从当前 parserinfo 对象(其本身默认为 False)中检索此值。
  • yearfirst – 是否将不明确的 3 整数日期(例如 01/05/09)中的第一个值解释为年份。如果为 True,则将第一个数字视为年份,否则将最后一个数字视为年份。如果设置为 None,则从当前 parserinfo 对象(其本身默认为 False)中检索该值。

【讨论】:

  • dateutil 的函数parse 是检测字符串中的日期,还是需要接收日期作为参数?
【解决方案2】:
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 的 '{0:0>2}'.format(day) 的信息,我添加了第 4 个解决方案,这似乎是最快的

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。
【解决方案3】:

如果您不想安装 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。在您的代码中并非如此,但对于包含您的 sn-p 的代码来说,这是有风险的。 - 最好把 ndates 做成一个生成器
  • @Tim Pietzcker Tim's + format 解决方案比您的纯 Tim's 解决方案 更短、更清晰、更快(请参阅我的答案中的编辑),那么....您的解决方案虽然被高估了,但并不是最好的,抱歉。
【解决方案4】:

您可以使用r'(\d+)\D(\d+)\D(\d+)' 之类的正则表达式通过re.findall 函数获取元组中的月、日和年。

然后只需将 2 位数年份与数字 2019 连接,然后使用要加入的分隔符然后返回:

'/'.join(the_list)

正如蒂姆所说:

要使天正常化,只需执行 '{0:0>2}'.format(day) 即可,对月份也是如此。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-09
    • 1970-01-01
    • 2014-09-26
    • 2015-01-21
    相关资源
    最近更新 更多