如何在PHP中用preg_replace过滤ICS文件,保留指定行并移除过期事件?
问题描述
我正在用PHP和JS开发一款应用,为避免代码冗余及过多依赖外部库,没采用Google API,而是通过ICS文件实现会议时间冲突检测功能,目前已自行编写JS解析器用于提取事件的起止时间。
当前存在以下问题:
- 包含10年事件记录的完整ICS文件传输给客户端,不仅导致代码冗余(多达15000行),还存在日程隐私泄露风险
- 需要过滤ICS文件,仅保留
BEGIN:VEVENT、END:VEVENT以及以DTSTART、DTEND开头的行;同时希望能移除已结束的事件,只保留DTEND时间在2023年及以后的事件
ICS文件片段示例
BEGIN:VEVENT DTSTART:20200907T170000Z DTEND:20200907T180000Z DTSTAMP:20221231T224008Z UID:(redacted) CREATED:20200820T161956Z DESCRIPTION: LAST-MODIFIED:20200820T162009Z LOCATION: SEQUENCE:1 STATUS:CONFIRMED SUMMARY:Contact (redacted name here) TRANSP:OPAQUE END:VEVENT BEGIN:VEVENT DTSTART;VALUE=DATE:20201101 DTEND;VALUE=DATE:20201104 DTSTAMP:20221231T224008Z UID:(redacted) CREATED:20200818T153428Z DESCRIPTION: LAST-MODIFIED:20200818T153428Z LOCATION: SEQUENCE:0 STATUS:CONFIRMED SUMMARY:..REDACTED PRIVATE INFO TRANSP:TRANSPARENT BEGIN:VALARM ACTION:DISPLAY DESCRIPTION:This is an event reminder TRIGGER:-P0DT4H0M0S END:VALARM END:VEVENT BEGIN:VEVENT
期望处理后结果示例
BEGIN:VEVENT DTSTART:20200907T170000Z DTEND:20200907T180000Z END:VEVENT BEGIN:VEVENT DTSTART;VALUE=DATE:20201101 DTEND;VALUE=DATE:20201104 END:VEVENT BEGIN:VEVENT...
PHP 实现代码
下面是分两步处理的PHP代码,先过滤每个VEVENT块内的无关行,再移除已过期的事件:
<?php // 读取原始ICS文件内容 $icsContent = file_get_contents('path/to/your/calendar.ics'); // 第一步:过滤每个VEVENT块,仅保留BEGIN:VEVENT、END:VEVENT、DTSTART、DTEND相关行 $filtered = preg_replace_callback( '/BEGIN:VEVENT(.*?)END:VEVENT/s', function($matches) { $eventLines = preg_split('/\r?\n/', $matches[1]); $keepLines = []; foreach ($eventLines as $line) { $trimmed = trim($line); if (empty($trimmed)) continue; // 低版本PHP替换为 substr($trimmed, 0, 7) === 'DTSTART' || substr($trimmed, 0, 5) === 'DTEND' if (str_starts_with($trimmed, 'DTSTART') || str_starts_with($trimmed, 'DTEND')) { $keepLines[] = $trimmed; } } return "BEGIN:VEVENT\n" . implode("\n", $keepLines) . "\nEND:VEVENT"; }, $icsContent ); // 第二步:移除DTEND早于2023年的VEVENT块 $finalFiltered = preg_replace_callback( '/BEGIN:VEVENT(.*?)END:VEVENT/s', function($matches) { $eventContent = $matches[1]; if (preg_match('/DTEND(?:;[^:]+)?:(\d{4})/', $eventContent, $dtendMatches)) { $year = (int)$dtendMatches[1]; if ($year < 2023) { return ''; } } return $matches[0]; }, $filtered ); // 清理多余空行 $finalFiltered = preg_replace('/\n{2,}/', "\n", $finalFiltered); // 输出或保存处理后的内容 echo $finalFiltered; // file_put_contents('path/to/filtered-calendar.ics', $finalFiltered); ?>
代码说明
第一步过滤无关行:
- 用
preg_replace_callback匹配每个完整的VEVENT块(s修饰符支持跨行匹配) - 拆分块内每行,仅保留
DTSTART、DTEND开头的行,重新组装成标准VEVENT结构 - 自动忽略空行和
VALARM等子块内容
- 用
第二步过滤过期事件:
- 再次遍历每个
VEVENT块,正则提取DTEND中的年份(兼容DTEND;VALUE=DATE这类带参数的格式) - 年份小于2023则移除整个事件块
- 再次遍历每个
清理空行:避免多次处理后出现连续空行,保证ICS格式整洁
注意事项
- 若PHP版本低于8.0,将
str_starts_with替换为substr($trimmed, 0, 7) === 'DTSTART'和substr($trimmed, 0, 5) === 'DTEND' - 代码针对标准ICS格式编写,若你的ICS存在多行折叠格式,需先展开折叠行再执行过滤
内容的提问来源于stack exchange,提问作者Shawn
相关产品推荐
相关产品推荐

