使用Regex以多种格式捕获日期

时间:2013-01-29 13:54:36

标签: php regex date

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

考虑以下句子片段:

  • " 2013年2月2日星期六研讨会的重点是[...]"
  • "情人节特别节目@ The Radisson,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中执行此操作,可能是第三方库或其他什么?

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

2 个答案:

答案 0 :(得分: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

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

答案 1 :(得分: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

通过使用fr.splayce.rel.matchers.en.Date.ALPHA(尚未在GitHub存储库中)表达PCREFlavor获得。它仅在有一个月时匹配,以字母形式表示(febfeb.february),….Date.ALL正则表达式也匹配{{1}等数字形式}更复杂。

此外,这个特殊的正则表达式符合您的示例,但可能仍然有点限制您的需求:

  • 不包括工作日
  • 与日期范围(仅匹配2/21/2013
  • 不匹配
  • 与年份不匹配,例如March 9th