
本文介绍两种高效解析形如“sep 04-08”或“sep 29-oct 01”的日期范围字符串的方法:一种基于正则预补全的单次替换+分割,另一种利用字符串左填充的无正则方案,兼顾性能与可读性。
本文介绍两种高效解析形如“sep 04-08”或“sep 29-oct 01”的日期范围字符串的方法:一种基于正则预补全的单次替换+分割,另一种利用字符串左填充的无正则方案,兼顾性能与可读性。
在处理日志、报表或用户输入中的简写日期范围时,常遇到省略重复月份的情况——例如 'Sep 04-08' 表示 Sep 04 至 Sep 08,而 'Sep 29-Oct 01' 则需保留完整月份信息。目标是将这类字符串统一拆解为两个标准格式的日期片段(如 ['Sep 04', 'Sep 08']),便于后续解析为 DateTime 对象或存入数据库。
✅ 方案一:正则预补全(推荐 —— 精准、声明式、一次处理)
核心思路是在连字符 - 后自动补全缺失的月份前缀,再统一按 - 分割。使用正则 ^(\D{4})\d{2}-\K(?=\d) 实现:
-
^(\D{4})捕获起始月份缩写(如"Sep ",注意空格也计入4字符); -
\d{2}-匹配起始日与连字符; -
\K重置匹配起点,丢弃此前所有已匹配内容; -
(?=\d)零宽断言,确保连字符后紧跟数字(即“结束月未出现”); - 替换为
$1,即将捕获的"Sep "插入到-后、数字前。
$examples = [
'Sep 04-08',
'Sep 29-Oct 01',
'Dec 25-Jan 01',
'Feb 14-16'
];
foreach ($examples as $example) {
$normalized = preg_replace('/^(\D{4})\d{2}-\K(?=\d)/', '$1', $example);
[$start, $end] = explode('-', $normalized);
echo "→ '{$start}' / '{$end}'\n";
}
输出:
→ 'Sep 04' / 'Sep 08' → 'Sep 29' / 'Oct 01' → 'Dec 25' / 'Jan 01' → 'Feb 14' / 'Feb 16'
✅ 优势:逻辑清晰、边界鲁棒(自动识别是否需补全)、扩展性强(支持任意长度月份缩写,只需调整 \D{4})。
⚠️ 注意:确保输入格式严格为 Mmm DD[-Mmm DD],且月份缩写统一为3字母+空格(共4字符);若存在 'Sept' 或 'September',需相应调整正则量词。
✅ 方案二:字符串左填充(轻量、无正则依赖)
利用“起始部分恒为6字符(如 'Sep 04'),而结束部分最短仅2字符(如 '08')”这一特性,直接对 to 部分进行左填充:
foreach ($examples as $example) {
[$from, $to] = explode('-', $example, 2);
// 用 $from 的前6字符(即 'Sep 0')左填充 $to 至长度6
$paddedTo = str_pad($to, 6, substr($from, 0, 6), STR_PAD_LEFT);
echo "→ '{$from}' / '{$paddedTo}'\n";
}
该方法本质是覆盖式拼接:str_pad('08', 6, 'Sep 04', STR_PAD_LEFT) → 'Sep 08'(因 '08' 占最后2位,前4位被 'Sep ' 填充)。
✅ 优势:零正则开销、代码极简、PHP内置函数保障稳定性。
⚠️ 注意:要求 $from 严格为 Mmm DD 格式(6字符),且 $to 仅含日部分(不含空格或多余前导零);若 $to 出现 'Oct 01' 等完整格式,则 str_pad 不生效,天然兼容。
? 总结建议
- 首选方案一(正则):适用于格式较规范、需明确语义和长期维护的项目;正则虽有学习成本,但一次写对即可稳健运行。
- 选用方案二(str_pad):适合性能敏感场景(如高频日志解析)、或团队禁用正则的约束环境;代码更易审计,调试直观。
- 两者均不依赖循环分割或多次
explode(),避免了原始提问中担忧的“资源消耗过高”问题。
最终,无论选择哪种方式,得到标准化的 ['Mmm DD', 'Mmm DD'] 数组后,均可轻松调用 DateTime::createFromFormat('M j', $part) 进行进一步时间计算。










