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-
相关产品推荐
相关产品推荐

