You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

日志行年份补全与月份转数字: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>';
?>

修正说明

  1. 预处理日志:遍历数组找到所有Jan行的索引,确定最后一次出现的Jan(最旧的记录)作为时间分界点。
  2. 放弃全局变量:改用局部变量控制年份逻辑,避免变量污染和重复赋值错误。
  3. 固定月份映射:用硬编码的月份映射表转换3字母月份为数字,比strtotime更可靠,避免日期解析歧义。
  4. 年份判断逻辑:
    • 无Jan记录时,所有行使用当前年份。
    • 有Jan记录时,根据行索引与最后Jan索引的关系判断:
      • 索引小于最后Jan的行(更新的日志):若月份大于1,年份为当前年份+1;否则用当前年份。
      • 索引大于等于最后Jan的行(旧日志):统一使用当前年份。

内容的提问来源于stack exchange,提问作者Cem Firat

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 02:35:27