package cn.iocoder.yudao.framework.excel.core.convert; import cn.idev.excel.converters.Converter; import cn.idev.excel.enums.CellDataTypeEnum; import cn.idev.excel.metadata.GlobalConfiguration; import cn.idev.excel.metadata.data.ReadCellData; import cn.idev.excel.metadata.data.WriteCellData; import cn.idev.excel.metadata.property.ExcelContentProperty; import lombok.extern.slf4j.Slf4j; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Arrays; import java.util.List; /** * 宽松的 LocalDate 转换器 * *

FastExcel 内置的 {@code LocalDateStringConverter} 只认 {@code yyyy-MM-dd}, * 而客户实际维护的表格里很常见 {@code 2026.3.30}、{@code 2026/3/30}、{@code 2026年3月30日} 这类写法, * 一旦解析不了会抛 {@code ExcelDataConvertException},导致整个导入直接失败(500), * 一条数据都进不去。这里做多格式兼容,解析不了时置空,交给业务侧按「必填」规则逐条提示。 * * @author 超级管理员 */ @Slf4j public class FlexibleLocalDateConverter implements Converter { /** * Excel 日期序列号的基准日(1899-12-30,兼容 Excel 的 1900 闰年 bug) */ private static final LocalDate EXCEL_EPOCH = LocalDate.of(1899, 12, 30); /** * 纯日期格式,按顺序尝试 */ private static final List DATE_FORMATTERS = Arrays.asList( DateTimeFormatter.ofPattern("yyyy-M-d"), DateTimeFormatter.ofPattern("yyyy.M.d"), DateTimeFormatter.ofPattern("yyyy/M/d"), DateTimeFormatter.ofPattern("yyyy年M月d日"), DateTimeFormatter.ofPattern("yyyyMMdd") ); /** * 带时间的格式,按顺序尝试后取日期部分 */ private static final List DATETIME_FORMATTERS = Arrays.asList( DateTimeFormatter.ofPattern("yyyy-M-d H:m:s"), DateTimeFormatter.ofPattern("yyyy-M-d H:m"), DateTimeFormatter.ofPattern("yyyy.M.d H:m:s"), DateTimeFormatter.ofPattern("yyyy/M/d H:m:s"), DateTimeFormatter.ofPattern("yyyy/M/d H:m") ); @Override public Class supportJavaTypeKey() { return LocalDate.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { // 不限定单元格类型:文本和数字都要支持 return null; } @Override public LocalDate convertToJavaData(ReadCellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (cellData == null) { return null; } // 1. 数字:按 Excel 日期序列号解析 if (CellDataTypeEnum.NUMBER.equals(cellData.getType())) { BigDecimal numberValue = cellData.getNumberValue(); return numberValue == null ? null : EXCEL_EPOCH.plusDays(numberValue.longValue()); } // 2. 文本:多格式依次尝试 String text = cellData.getStringValue(); if (text == null || text.trim().isEmpty()) { return null; } text = text.trim(); for (DateTimeFormatter formatter : DATE_FORMATTERS) { try { return LocalDate.parse(text, formatter); } catch (Exception ignored) { // 继续尝试下一个格式 } } for (DateTimeFormatter formatter : DATETIME_FORMATTERS) { try { return LocalDateTime.parse(text, formatter).toLocalDate(); } catch (Exception ignored) { // 继续尝试下一个格式 } } log.warn("[convertToJavaData][日期({}) 无法识别,已置空]", text); return null; } @Override public WriteCellData convertToExcelData(LocalDate value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { if (value == null) { return new WriteCellData<>(""); } return new WriteCellData<>(value.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"))); } }