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

Laravel 7导入Excel报Invalid date value日期解析异常问题

Laravel 7 Excel考勤导入功能异常修复方案

问题表现

  • 导入考勤数据时抛出Invalid date value错误
  • 写入数据库的日期、时间字段为Excel原生序列化数值(如日期44713、时间0.42361111111111类浮点数)
  • 迟到/早退时长计算结果异常,早退固定返回23:59:59

核心问题原因

  • 列索引错位:提供的Excel表头顺序为id/date/status/check-in/check-out,从0开始计数的数组索引对应关系为0=id、1=date、2=status、3=check-in、4=check-out,原代码错误使用row[2]读取日期、row[4]读取签到、row[5]读取签退,读取到了错误的单元格值
  • 时间对比逻辑错误:系统配置的上下班时间仅包含时分秒,直接解析后默认取当前系统日期作为日期维度,和考勤记录的实际日期不匹配,跨日期对比导致判断逻辑完全失效
  • 纯时间解析异常:Excel中无日期的纯时间单元格解析后默认返回1900-01-01对应的时间点,和当前日期的上下班时间计算差值时,跨度达到数十年,取时分秒部分刚好返回23:59:59的异常值
  • 重复性能损耗:每行导入数据都单独查询一次系统配置,产生不必要的数据库查询
  • 空值兼容缺失:未对签到、签退空单元格做判断,空值解析时直接抛出错误

修复步骤

  • 修正所有单元格读取的索引值,和Excel实际列顺序对齐
  • 将系统配置的上下班时间,和当前行的考勤日期拼接,生成对应考勤日的准确上下班时间点,避免跨日期对比
  • 兼容Excel数值格式、字符串格式两种日期/时间存储形式,统一转换为Carbon实例后格式化,再赋值给模型字段
  • 将系统配置查询逻辑移入导入类构造函数,整个导入过程仅查询一次配置
  • 增加签到、签退空值判断,空值时跳过时长计算逻辑
  • 提前判断考勤记录是否存在,避免重复写入

修复后控制器代码

public function import(Request $request){
    $request->validate([
        'import_file'=>'required|file|mimes:xlsx,xls,csv'
    ]);
    Excel::import(new ImportAttendanceExcel, $request->file('import_file'));
    return back()->with('message','考勤文件导入成功');
}

修复后导入类代码

<?php

namespace App\Imports;

use App\Models\Attendance;
use App\Models\Setting;
use Carbon\Carbon;
use Illuminate\Support\Facades\Auth;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithStartRow;
use PhpOffice\PhpSpreadsheet\Shared\Date;

class ImportAttendanceExcel implements ToModel, WithStartRow
{
    protected $workStartTime;
    protected $workEndTime;

    public function __construct()
    {
        // 导入初始化时一次性读取系统配置,避免每行重复查询
        $settings = Setting::find(1);
        // 仅保留上下班时间的时分秒部分
        $this->workStartTime = Carbon::parse($settings->opening_time)->format('H:i:s');
        $this->workEndTime = Carbon::parse($settings->ending_time)->format('H:i:s');
    }

    public function model(array $row)
    {
        $userId = $row[0];
        // 用户ID为空直接跳过当前行
        if (empty($userId)) {
            return null;
        }

        // 解析考勤日期,兼容Excel数值格式、字符串格式
        $attendanceDate = is_numeric($row[1]) 
            ? Carbon::instance(Date::excelToDateTimeObject($row[1])) 
            : Carbon::parse($row[1]);
        $attendanceDateStr = $attendanceDate->format('Y-m-d');

        // 当日考勤已存在则跳过
        $existAttendance = Attendance::where('user_id', $userId)
            ->where('attendance_date', $attendanceDateStr)
            ->exists();
        if ($existAttendance) {
            return null;
        }

        // 拼接当前考勤日的上下班完整时间
        $todayWorkStart = Carbon::parse($attendanceDateStr . ' ' . $this->workStartTime);
        $todayWorkEnd = Carbon::parse($attendanceDateStr . ' ' . $this->workEndTime);

        // 解析签到时间、计算迟到时长
        $checkInStr = null;
        $lateDuration = null;
        if (!empty($row[3])) {
            $checkIn = is_numeric($row[3])
                ? Carbon::instance(Date::excelToDateTimeObject($row[3]))->setDateFrom($attendanceDate)
                : Carbon::parse($attendanceDateStr . ' ' . $row[3]);
            $checkInStr = $checkIn->format('Y-m-d H:i:s');
            
            if ($checkIn->gt($todayWorkStart)) {
                $lateDuration = $checkIn->diff($todayWorkStart)->format('%H:%I:%S');
            }
        }

        // 解析签退时间、计算早退时长
        $checkOutStr = null;
        $earlyDuration = null;
        if (!empty($row[4])) {
            $checkOut = is_numeric($row[4])
                ? Carbon::instance(Date::excelToDateTimeObject($row[4]))->setDateFrom($attendanceDate)
                : Carbon::parse($attendanceDateStr . ' ' . $row[4]);
            $checkOutStr = $checkOut->format('Y-m-d H:i:s');
            
            if ($checkOut->lt($todayWorkEnd)) {
                $earlyDuration = $checkOut->diff($todayWorkEnd)->format('%H:%I:%S');
            }
        }

        return new Attendance([
            'user_id' => $userId,
            'created_by' => Auth::id(),
            'attendance_date' => $attendanceDateStr,
            'attendance_status' => $row[2],
            'check_in' => $checkInStr,
            'check_out' => $checkOutStr,
            'late' => $lateDuration,
            'early' => $earlyDuration,
        ]);
    }

    public function startRow(): int
    {
        return 2;
    }
}

配套校验建议

  • 提前确认attendances表字段类型:attendance_date使用date类型,check_in/check_out使用datetime或time类型,late/early使用time或varchar类型存储时长字符串即可
  • 正式导入前可增加表头校验逻辑,避免用户调整Excel列顺序导致索引错位
  • 若签到签退仅需存储时间,格式化时只保留H:i:s部分即可

内容的提问来源于stack exchange,提问作者Shan Md Shazarul Haque 171-15-

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 18:54:27