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 转换器
|
*
|
* <p>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<LocalDate> {
|
|
/**
|
* Excel 日期序列号的基准日(1899-12-30,兼容 Excel 的 1900 闰年 bug)
|
*/
|
private static final LocalDate EXCEL_EPOCH = LocalDate.of(1899, 12, 30);
|
|
/**
|
* 纯日期格式,按顺序尝试
|
*/
|
private static final List<DateTimeFormatter> 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<DateTimeFormatter> 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<LocalDate> 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")));
|
}
|
|
}
|