tuqin
2 天以前 ffec90c897e9bd78e88b9fa590a5dd75d26b82c8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
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")));
    }
 
}