【问题标题】:Using Regex to catch dates in many formats使用正则表达式捕获多种格式的日期
【发布时间】:2013-01-13 03:00:06
【问题描述】:

我正在开发一个应用程序,它会抓取本地网站以创建即将发生的事件的数据库,并且我正在尝试使用正则表达式来捕获尽可能多的日期格式。

考虑以下句子片段:

  • “2013 年 2 月 2 日星期六研讨会的重点将是 [...]”
  • “2 月 14 日丽笙酒店的情人节特辑”
  • “2 月 15 日星期五,一个特别的好莱坞主题 [...]”
  • “2 月 8 日星期五儿童游戏研讨会”
  • “3 月 9 日至 11 日在旧 [...] 举办工艺研讨会”

我希望能够扫描这些并捕获尽可能多的日期。目前我正在以一种可能有缺陷的方式执行此操作(我不擅长正则表达式),一个接一个地通过几个正则表达式语句,就像这样

/([0-9]+?)(st|nd|rd|th) (of)? (Jan|Feb|Mar|etc)/i
/([0-9]+?)(st|nd|rd|th) (of)? (January|February|March|Etcetera)/i
/(Jan|Feb|Mar|etc) ([0-9]+?)(st|nd|rd|th)/i
/(January|February|March|Etcetera) ([0-9]+?)(st|nd|rd|th)/i

我可以将所有这些合并到一个巨大的正则表达式语句中,但似乎在 php 中必须有一种更清洁的方式来执行此操作,也许是第三方库或其他什么?

编辑:上面的正则表达式可能有错误 - 它只是作为一个例子。

【问题讨论】:

  • 到底是什么问题?您是否正在寻找一个与您已有的四个等价的正则表达式?是否有某些原因您不能使用多个?
  • @erisco,我想知道 A) 所需的四个以上表达式是否可以巧妙地合并到一个表达式中,或者 B) 是否有一个库或函数可以做到这一点,就像下面建议的那样跨度>

标签: php regex date


【解决方案1】:

对于这种可能很复杂的正则表达式,我倾向于将其分解为可以单独进行单元测试、维护和演进的简单部分。

我使用REL,这是一种 DSL(在 Scala 中),它允许您重新组装和重用您的正则表达式片段。这样,您可以在每个部分上定义您的正则表达式 like these Date matchersunit test

此外,您的单元/规范测试可以作为您的正则表达式的文档,指示匹配的内容和不匹配的内容(这对于正则表达式往往很重要)。

在即将发布的 REL (0.3) 版本中,您将能够直接导出正则表达式,例如 PCRE(因此,PHP)风格以独立使用它……目前仅 JavaScript 和 .NET 翻译在github存储库。使用最新(尚未公开提交)的快照,英文字母数字日期正则表达式的 PCRE 风格是这样的:

/(?:(?:(?<!\d)(?<a_d1>(?>(?:(?:[23]?1)st|(?:2?2)nd|(?:2?3)rd|(?:[12]?[4-9]|[123]0)th)\b|0[1-9]|[12][0-9]|3[01]|[1-9]|[12][0-9]|3[01]))(?: ?+(?:of )?+))(?>(?<a_m1>jan(?>uary|\.)?|feb(?>ruary|r?\.?)?|mar(?>ch|\.)?|apr(?>il|\.)?|may|jun(?>e|\.)?|jul(?>y|\.)?|aug(?>ust|\.)?|sep(?>tember|t?\.?)?|oct(?>ober|\.)?|nov(?>ember|\.)?|dec(?>ember|\.)?))|(?:\b(?>(?<a_m2>jan(?>uary|\.)?|feb(?>ruary|r?\.?)?|mar(?>ch|\.)?|apr(?>il|\.)?|may|jun(?>e|\.)?|jul(?>y|\.)?|aug(?>ust|\.)?|sep(?>tember|t?\.?)?|oct(?>ober|\.)?|nov(?>ember|\.)?|dec(?>ember|\.)?)))(?:(?:(?: ?+)(?<a_d2>(?>(?:(?:[23]?1)st|(?:2?2)nd|(?:2?3)rd|(?:[12]?[4-9]|[123]0)th)\b|0[1-9]|[12][0-9]|3[01]|[1-9]|[12][0-9]|3[01]))(?!\d))?))(?:(?:,?+)(?:(?:(?: ?)(?<a_y>(?:1[7-9]|20)\d\d|'?+\d\d))(?!\d))|(?<=\b|\.))/i

通过使用PCREFlavor 表达fr.splayce.rel.matchers.en.Date.ALPHA 获得(尚未在GitHub 存储库中)。只有当有月份时才会匹配,以字母形式(febfeb.february)表示,….Date.ALL 正则表达式也匹配像 2/21/2013 这样的数字形式更复杂。

此外,这个特定的正则表达式与您的示例相匹配,但对于您的需求可能仍然有点限制:

  • 不包括工作日
  • 它将不匹配日期范围(仅匹配March 9th
  • 它不会与第一个年份匹配,例如2013, jan. 14th

【讨论】:

    【解决方案2】:

    我写了一个函数,它使用strtotime()从文本中提取日期:

    function parse_date_tokens($tokens) {
      # only try to extract a date if we have 2 or more tokens
      if(!is_array($tokens) || count($tokens) < 2) return false;
      return strtotime(implode(" ", $tokens));
    }
    
    function extract_dates($text) {
      static $patterns = Array(
        '/^[0-9]+(st|nd|rd|th|)?$/i', # day
        '/^(Jan(uary)?|Feb(ruary)?|Mar(ch)?|etc)$/i', # month
        '/^20[0-9]{2}$/', # year
        '/^of$/' #words
      );
      # defines which of the above patterns aren't actually part of a date
      static $drop_patterns = Array(
        false,
        false,
        false,
        true
      );
      $tokens = Array();
      $result = Array();
      $text = str_word_count($text, 1, '0123456789'); # get all words in text
    
      # iterate words and search for matching patterns
      foreach($text as $word) {
        $found = false;
        foreach($patterns as $key => $pattern) {
          if(preg_match($pattern, $word)) {
            if(!$drop_patterns[$key]) {
              $tokens[] = $word;
            }
            $found = true;
            break;
          }
        }
    
        if(!$found) {
          $result[] = parse_date_tokens($tokens);
          $tokens = Array();
        }
      }
      $result[] = parse_date_tokens($tokens);
    
      return array_filter($result);
    }
    
    # test
    $texts = Array(
      "The focus of the seminar, on Saturday 2nd February 2013 will be [...]",
      "Valentines Special @ The Radisson, Feb 14th",
      "On Friday the 15th of February, a special Hollywood themed [...]",
      "Symposium on Childhood Play on Friday, February 8th",
      "Hosting a craft workshop March 9th - 11th in the old [...]"
    );
    
    $dates = extract_dates(implode(" ", $texts));
    echo "Dates: \n";
    foreach($dates as $date) {
      echo "  " . date('d.m.Y H:i:s', $date) . "\n";
    }
    

    这个输出:

    Dates: 
      02.02.2013 00:00:00
      14.02.2013 00:00:00
      15.02.2013 00:00:00
      08.02.2013 00:00:00
      09.03.2013 00:00:00
    

    这个解决方案可能并不完美,当然也有缺陷,但它是解决您的问题的一个非常简单的解决方案。

    【讨论】:

    • 那么它是否因为“of”而错过了“2 月 15 日”?
    • 谢谢,我没注意到。我修好了。
    猜你喜欢
    • 2021-09-04
    • 2021-12-10
    • 2012-07-15
    • 2016-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-21
    相关资源
    最近更新 更多