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")));
}
}