日志行年份补全与月份转数字:PHP代码修正需求
日志年份补全与月份转换代码修正
背景信息
- 日志文件会定期复制读取,行内未标注年份。
- 日志中的月份是连续的。
- 1月之前的月份(如11月、12月)均属于对应1月年份的下一年(日志按时间倒序排列)。
- 规则细节:
- 若日志无1月记录:所有行均属于当前年份。
- 若日志存在一次或多次1月记录:以最后一次出现的1月为时间分界点,该记录及之后的旧日志属于当前年份;分界点之前的新日志中,月份大于1的行属于当前年份+1。
原问题代码
测试日志数组
$arr = [ // 首行是最新的Dec,后续出现Jan,预期首行年份为2023,Jan及之后为2022 "Dec 23 21:37:56 hello", "Jan 12 02:08:23 hello", "Jan 16 17:34:33 hello", "Feb 4 12:21:09 hello", "Mar 19 17:07:26 hello", "Apr 1 00:00:03 hello", "Apr 12 23:07:39 hello", "May 21 04:09:34 hello", "Jun 7 23:34:56 hello", "Jul 1 14:45:34 hello", "Aug 13 11:37:23 hello", "Sep 29 07:36:03 hello", "Oct 30 09:01:00 hello", "Nov 10 11:00:03 hello", "Dec 25 21:47:51 hello" ];
原有年份判断函数
function setYear() { global $arr, $y; $first = explode(' ', $arr[array_key_first($arr)]); // 若首行不是Jan,则设为上一年 if (!in_array('01', $first)) { $y = date("Y", strtotime("-1 year")); } else { $y = date("Y"); } return $y; }
原有转换代码
$arr = preg_replace_callback( '/^(\w+)\s+(\d+)\s/', function ($matches) { global $y; $yy = setYear($y); return date($yy . ' m d', strtotime($matches[0] . ' ' . date("Y"))) . ' '; }, $arr ); echo '<pre>'; print_r($arr); echo '</pre>';
问题表现
意外结果
Array ( [0] => 2022 12 23 21:37:56 hello [1] => 2022 01 12 02:08:23 hello [2] => 2022 01 16 17:34:33 hello [3] => 2022 02 04 12:21:09 hello // ... [13] => 2022 11 10 11:00:03 hello [14] => 2022 12 25 21:47:51 hello )
预期结果
Array ( [0] => 2023 12 23 21:37:56 hello [1] => 2022 01 12 02:08:23 hello [2] => 2022 01 16 17:34:33 hello [3] => 2022 02 04 12:21:09 hello // ... [13] => 2022 11 10 11:00:03 hello [14] => 2022 12 25 21:47:51 hello )
修正后的代码
<?php $arr = [ "Dec 23 21:37:56 hello", "Jan 12 02:08:23 hello", "Jan 16 17:34:33 hello", "Feb 4 12:21:09 hello", "Mar 19 17:07:26 hello", "Apr 1 00:00:03 hello", "Apr 12 23:07:39 hello", "May 21 04:09:34 hello", "Jun 7 23:34:56 hello", "Jul 1 14:45:34 hello", "Aug 13 11:37:23 hello", "Sep 29 07:36:03 hello", "Oct 30 09:01:00 hello", "Nov 10 11:00:03 hello", "Dec 25 21:47:51 hello" ]; // 预处理:找到所有Jan行的索引,取最后一个(最旧的Jan记录) $janIndices = []; foreach ($arr as $index => $line) { if (str_starts_with(trim($line), 'Jan')) { $janIndices[] = $index; } } $lastJanIndex = !empty($janIndices) ? end($janIndices) : -1; $currentYear = (int)date("Y"); // 月份映射表,避免strtotime解析错误 $monthMap = [ 'Jan' => '01', 'Feb' => '02', 'Mar' => '03', 'Apr' => '04', 'May' => '05', 'Jun' => '06', 'Jul' => '07', 'Aug' => '08', 'Sep' => '09', 'Oct' => '10', 'Nov' => '11', 'Dec' => '12' ]; $processedArr = []; foreach ($arr as $index => $line) { // 拆分日志行的月份、日期和剩余内容 preg_match('/^(\w+)\s+(\d+)\s+(.*)$/', $line, $matches); if (!isset($matches[1], $matches[2], $matches[3])) { $processedArr[] = $line; continue; } $monthStr = $matches[1]; $day = str_pad($matches[2], 2, '0', STR_PAD_LEFT); $rest = $matches[3]; // 确定当前行的年份 if ($lastJanIndex === -1) { // 无Jan记录,全部用当前年份 $year = $currentYear; } else { if ($index < $lastJanIndex) { // 最后Jan之前的新日志:月份>1则年份为当前年份+1 $monthNum = (int)$monthMap[$monthStr]; $year = $monthNum > 1 ? $currentYear + 1 : $currentYear; } else { // 最后Jan及之后的旧日志:用当前年份 $year = $currentYear; } } // 拼接成目标格式 $processedLine = sprintf("%d %s %s %s", $year, $monthMap[$monthStr], $day, $rest); $processedArr[] = $processedLine; } echo '<pre>'; print_r($processedArr); echo '</pre>'; ?>
修正说明
- 预处理日志:遍历数组找到所有Jan行的索引,确定最后一次出现的Jan(最旧的记录)作为时间分界点。
- 放弃全局变量:改用局部变量控制年份逻辑,避免变量污染和重复赋值错误。
- 固定月份映射:用硬编码的月份映射表转换3字母月份为数字,比
strtotime更可靠,避免日期解析歧义。 - 年份判断逻辑:
- 无Jan记录时,所有行使用当前年份。
- 有Jan记录时,根据行索引与最后Jan索引的关系判断:
- 索引小于最后Jan的行(更新的日志):若月份大于1,年份为当前年份+1;否则用当前年份。
- 索引大于等于最后Jan的行(旧日志):统一使用当前年份。
内容的提问来源于stack exchange,提问作者Cem Firat
相关产品推荐
相关产品推荐

