我正在开发一个应用程序,该应用程序会删除本地网站以创建即将发生的事件的数据库,并且我尝试使用正则表达式来捕获尽可能多的日期格式。
考虑以下句子片段:
我希望能够扫描这些并尽可能多地捕捉日期。目前我在这可能是一种有缺陷的方式(我在正则表达式上不是很好)这样做,一个接一个地经历几个正则表达式语句,像这样
/([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中执行此操作,可能是第三方库或其他什么?
编辑:上面的正则表达式可能有错误 - 它只是作为一个例子。
答案 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 matchers和unit 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
获得。它仅在有一个月时匹配,以字母形式表示(feb
,feb.
或february
),….Date.ALL
正则表达式也匹配{{1}等数字形式}更复杂。
此外,这个特殊的正则表达式符合您的示例,但可能仍然有点限制您的需求:
2/21/2013
)March 9th