【问题标题】:A "smart" (forgiving) date parser?“智能”(宽容)日期解析器?
【发布时间】:2010-11-09 08:08:31
【问题描述】:

我必须将一个非常大的数据集从一个系统迁移到另一个系统。其中一个“源”列包含一个日期,但实际上是一个没有约束的字符串,而目标系统要求一个格式为 yyyy-mm-dd 的日期。

许多(但不是全部)源日期格式为 yyyymmdd。因此,为了将它们强制转换为预期的格式,我会(在 Perl 中):

return "$1-$2-$3" if ($val =~ /(\d{4})[-\/]*(\d{2})[-\/]*(\d{2})/);

当源日期远离“通用”yyyymmdd 时,就会出现问题。目标是在放弃之前尽可能多地挽救约会对象。示例源字符串包括:

21/3/1998, 2004 年 3 月, 2001年, 97 年 3 月 4 日

我可以尝试将我能找到的尽可能多的示例与上面的一系列正则表达式进行匹配。

但是有什么更聪明的方法可以做吗?我不是在重新发明轮子吗?有没有图书馆在做类似的事情?我找不到任何相关的谷歌搜索“原谅日期解析器”。 (任何语言都可以)。

【问题讨论】:

  • 3/4/97 - 是 3 月 4 日还是 4 月 3 日?
  • 取决于您所在的地区。在美国,那是 3 月 4 日。在美国以外,可能是 4 月 3 日。
  • 我想大多数日期工具都可以设置默认选项来处理像 3/4/97 这样的情况。快速浏览一下,下面列出的 Perl 模块中至少有两个具有这样的选项。

标签: regex perl parsing date


【解决方案1】:

Date::Manip 是你的朋友,因为它采用美国格式,所以只有四分之一失败,使用 Date_Init 你可以得到四分之四。

如果您有不同的格式(即,前一个月,反之亦然),您必须以不同方式解析它们,一次使用美国日期格式,下一次使用非美国日期格式。这在模棱两可时尤其重要,例如您的 3/4/97 示例,因为如果是 21/3,它就会失败并且您可以判断格式错误。

vinko@mithril:~$ more date.pl
use strict;
use warnings;
use Date::Manip;

my @a;
push @a, "March 2004";
push @a, "2001";
push @a, "3/4/97";
push @a, "21/3/1998";
Date_Init("DateFormat=non-US");
for my $d (@a) {
    print "$d\n";
    print ParseDate($d)."\n";
};
vinko@mithril:~$ perl date.pl
March 2004
2004030100:00:00
2001
2001010100:00:00
3/4/97
1997040300:00:00
21/3/1998
1998032100:00:00

【讨论】:

    【解决方案2】:

    您在寻找Date::Parse 模块吗?

    【讨论】:

    • 我不了解 perl,但至少在 C# 中,bogstandard DateTime.TryParse() 将接受相当多样化的不同日期格式。您应该注意那些它不接受的内容并将它们特殊化。在这种情况下,可能整行都需要手动处理。
    【解决方案3】:

    我最终提取了一个包含 200 多个实际出现在数据集中的日期示例的测试集。有些行为不端,有些则完全病倒(例如“01010”)。

    我尝试了所有能找到的现有 Perl 模块,但成功率太低。最终,我重新发明了自己的轮子,成功率超过了 98%。

    我的算法是一系列越来越模糊的识别器,从严格有效的日期下降到总猜测范围。第一个返回“成功”结果的获胜。在该堆栈的中间,我有一个“主要”识别器,它执行以下操作:

    • 在任何地方解析字符串中的数字集。法语和英语的“月份名称”也可以识别。

    • 对于它们中的每一个,我将它们放在三个桶中:年份候选、月份候选、日候选。例如,“13”将在“可能的年份”存储桶中,在“可能的日子”存储桶中。当然,“二月”只会出现在“几个月”中。在每个桶中,该值都被标记为“合理性级别”,这是一个取决于许多事情的任意数字。例如,2010 年比 10 年更合理。

    • 查看三个桶中的每一个。如果其中任何一个只有一个项目,则它是该存储桶的 值。它也会从其他存储桶中删除。

    • 按顺序(年、月、日)在它们各自的桶中查找剩余的缺失值,取最合理的那个。在平局的情况下,取最后一个出现在字符串中的那个(实际上,那些有更高的合理性)。这条规则在 2010 年 3 月 7 日打破了 2010 年 7 月 3 日,因为我需要在法国。如果情况适用,请从其他存储桶中删除该值。

    • 如果缺少任何值,请使用默认值(例如,我使用 8191 作为默认年份,这是我的目标系统中允许的最大值)。

    整个事情都是非常启发式的,但符合我的要求,即拥有垃圾比丢失信息更好。

    【讨论】:

      【解决方案4】:

      你也可以看看DateTime::Format::Flexible

      根据它的描述,它就在你的小巷里:

      如果您曾经使用过某个程序 这让你输入日期 某种方式和想法“为什么不能 计算机只是弄清楚我是什么日期 想要吗?”,这个模块适合你。

      DateTime::Format::Flexible 尝试 取你给它的任何字符串并解析 将其转换为 DateTime 对象。

      我刚才使用这个模块运行了一个版本的 Vinko 脚本,得到了类似的结果。除了最后一种情况(1998 年 3 月 21 日),一切都很好。与 Date::Manip 一样,您可以通过显式设置参数 (european => 1) 相对轻松地处理此问题。 Danbystrom 的评论说明了为什么此类案件需要人工监督。

      【讨论】:

      • datetime.perl.org/?Modules 说:“DateTime::Format::Flexible - 主要是 DateTime::Format::Natural 的子集,不推荐使用。请改用 DateTime::Format::Natural(并提交如果需要,修补程序以改进其解析;)"
      • 我看到了,但我也在模块自己的页面上看到了这个:“截至 2008 年 3 月,DateTime 网站 datetime.perl.org/?Modules 将此模块列在“令人困惑”下,并建议使用 DateTime::Format ::Natural。不幸的是,我不同意。DateTime::Format::Natural 目前在我的解析测试中失败了 2000 多次。DateTime::Format::Flexible 支持与 DateTime::Format::Natural 不同类型的日期/时间字符串. 我认为这两者都有用处。”由于 OP 要求“原谅”,我认为这值得一看。
      【解决方案5】:

      不是 perl,而是 this .NET library 将解析范围广泛的日期/时间字符串。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-05-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-04-27
        • 2010-09-11
        相关资源
        最近更新 更多