2

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

考虑以下句子片段:

  • “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 中必须有一种更清洁的方式来执行此操作,也许是第三方库之类的?

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

4

2 回答 2

4

我编写了一个函数,它使用以下方法从文本中提取日期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

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

于 2013-01-29T14:59:02.470 回答
1

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

我使用REL,一种 DSL(在 Scala 中),它允许您重新组装和重用您的正则表达式片段。这样,您可以像这些日期匹配器和每个部分的单元测试一样定义您的正则表达式。

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

在即将发布的 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

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

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

  • 它不包括工作日
  • 它将不匹配日期范围(仅匹配March 9th
  • 它与第一个年份不匹配,例如2013, jan. 14th
于 2013-01-29T14:39:52.410 回答