
本文介绍在 PHP 中利用 preg_match_all 配合合理正则表达式,从形如 "7 years 11 months 3 weeks" 的字符串中准确提取带单位的时间片段(如 "7 years"),并支持零值与多种格式变体。
本文介绍在 php 中利用 `preg_match_all` 配合合理正则表达式,从形如 `"7 years 11 months 3 weeks"` 的字符串中准确提取带单位的时间片段(如 `"7 years"`),并支持零值与多种格式变体。
在处理自然语言风格的时间描述字符串(例如 "0 years 1 month 0 weeks" 或 "7 years 11 months 3 weeks")时,目标不是「分割」字符串,而是「提取」每个“数值 + 单位”的完整语义单元。此时,preg_split 并非最优选择——它旨在按分隔符切分,而本例中并无统一、可靠的分隔符(空格既出现在数字与单位之间,也出现在单位之间),强行用 preg_split 容易误切或逻辑复杂化。
正确思路是:匹配而非分割。使用 preg_match_all() 直接捕获所有符合「数字 + 空格 + 单词」模式的子串。
✅ 推荐方案:preg_match_all 提取完整项
$text = "7 years 11 months 3 weeks";
preg_match_all('/d+s+w+/', $text, $matches);
// $matches[0] 即为所需结果
$result = $matches[0];
print_r($result);
// 输出:
// Array
// (
// [0] => 7 years
// [1] => 11 months
// [2] => 3 weeks
// )
该正则 /\d+\s+\w+/ 含义清晰:
- \d+:匹配一个或多个数字(支持 0、11 等);
- \s+:匹配一个或多个空白字符(兼容空格、制表符等);
- \w+:匹配连续的单词字符(字母、数字、下划线),足以覆盖 years、months、weeks、month、week 等常见单位。
? 为什么不用 preg_split?
原问题中尝试的 (?!d+)(?:s)(?!D+) 是错误的负向断言组合,逻辑混乱且无法满足需求。preg_split 要求明确的「分界点」,而本例中空格是内容的一部分(如 "7 years" 内部也有空格),并非纯粹分隔符——强行分割将破坏语义完整性。
? 进阶:分离数值与单位(结构化处理)
若后续需分别处理数字和单位(例如转为整型、统一单复数、计算总天数),建议使用捕获组:
$text = "0 years 1 month 0 weeks";
preg_match_all('/(d+)s+(w+)/', $text, $matches, PREG_SET_ORDER);
// $matches 现为二维数组,每项含 [full_match, number, unit]
$parsed = [];
foreach ($matches as $match) {
$parsed[] = [
'value' => (int)$match[1],
'unit' => strtolower($match[2])
];
}
print_r($parsed);
// 输出:
// Array
// (
// [0] => Array ( [value] => 0 [unit] => years )
// [1] => Array ( [value] => 1 [unit] => month )
// [2] => Array ( [value] => 0 [unit] => weeks )
// )
⚠️ 注意事项与兼容性建议
- 单位扩展性:若输入可能含连字符单位(如 man-weeks)或撇号(如 day's),请将 \w+ 替换为 \S+(非空白字符序列),即 /\d+\s+\S+/;
- 浮点数支持:如需匹配 "1.5 years",改用 /\d*\.?\d+\s+\S+/(注意小数点需转义);
- 大小写与空格健壮性:添加修饰符 i(忽略大小写)和 u(UTF-8 安全),例如 /\d+\s+\w+/iu;
- 边界安全:若字符串中混有其他数字文本(如 "See section 5 for details"),建议添加词边界 \b:/\b\d+\s+\w+\b/,避免误匹配。
✅ 总结
| 场景 | 推荐方法 | 正则示例 |
|---|---|---|
| 提取 "N unit" 字符串数组 | preg_match_all('/\d+\s+\w+/', $str, $m) | '/\d+\s+\w+/' |
| 结构化解析(数值 + 单位) | preg_match_all('/(\d+)\s+(\w+)/', $str, $m, PREG_SET_ORDER) | '/($d+)\s+(\w+)/' |
| 支持连字符、特殊符号单位 | 将 \w+ → \S+ | '/\d+\s+\S+/' |
牢记:对「固定模式的复合词」,优先考虑 match 而非 split——它更直观、更可靠、更易维护。










