EasyExcel3.0.5 生成Excel,设置日期时间类型单元格格式不生效问题解决

由于需要对接外部系统,通过Excel互传文件,对方解析的过程中要求某一个sheet页中的部分单元格为时间格式(不是说文本是时间,而是指单元格格式为时间), 因此而进行的一系列尝试和改造。

需求,生成单元格格式为h:mm的时间类型的Excel

1. 加注解

通过网上查找类似问题,一般都建议加上注解来解决,通过在Excel生成类DTO的成员变量上增加注解

java 复制代码
@ContentStyle(dataFormat = 20)
@DateTimeFormat("h:mm")
@ExcelProterty("工作时间")
private String workTime;

结果是单元格格式设置成功,但是未作用于文本内容; 什么意思呢,就是鼠标右键这个单元格,查看单元格格式,是所需要的自定义h:mm格式,但里边的内容并没有按照格式显示,还是文本格式,需要手动双击进入一下单元格,再点击其他单元格后,文本生效。 经测试,外部系统读取时,读到的也是文本,并非日期。 效果如果:

双击后,单元格格式起作用

这里能明显的感觉到,单元格格式是已经附上去了,只是最后往里边写文本的时候,还是默认的文本模式,所以生成后是以上的情况,需要点击一下,让excel自己去识别转换;

2. 加Converter

另一种方案是加converter, 就是把字段转换一下,变成日期写入

java 复制代码
public class DateConverter implements Converter<String> {

    private static final String HH_MM_SS = "HH:mm:ss";

    @Override  
    public Class<Date> supportJavaTypeKey(){ return Date.class; }

    @Override  
    public WriteCellData<Date> convertToExcelData(WriteConverterContext<String> context) throws Exception {  
        String date = context.getValue();  
        if (date == null){ return null; }

        SimpleDateFormat sdf = new SimpleDateFormat(HH_MM_SS);  
        return new WriteCellData<>(sdf.parse(date));  
    }  
}

同时在实体类成员变量的注解上增加converter

java 复制代码
@ExcelProterty(value = "工作时间",converter = DateConverter.class)
private String workTime;

保存运行测试,问题依旧,情况依然是单元格格式生效,文本不生效;

3. 加处理Handler

后有查询到一种说法,是需要先赋值,再更改单元格,所以需要加处理Handler,代码如下:

java 复制代码
public class ExcelCellWriteHandler implements CellWriteHandler {

    @Override  
    public void afterCellDispose(CellWriteHandlerContext context) {  
        // 3.0 设置单元格为文本  
        Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();  
        WriteCellData<?> cellData = context.getFirstCellData();  
        WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();  
        DataFormatData dataFormatData = new DataFormatData();  
        //设置什么类型的格式,可以搜索easyExcel的格式代表的序号去设置(20为 "H:mm" 格式)  
        dataFormatData.setIndex((short) 20);  
        writeCellStyle.setDataFormatData(dataFormatData);  
        cellData.setWriteCellStyle(writeCellStyle);  
    }

}

然后需要在构建Excel的时候,注册上处理类

java 复制代码
EasyExcelFactory.writerSheet(0, "工作时间").head(WorkTimeExcelDTO.class).registerWriteHandler(new ExcelCellWriteHandler()).build();

保存运行测试,问题依旧,情况依然是单元格格式生效,文本不生效;

4. 改变DTO成员变量类型

因为考虑到可能是类型的问题,将成员变量改为Date类型

java 复制代码
private Date workTime;

生成出来的文本内容还有年月日,不符合要求。故考虑 LocalTime

java 复制代码
private LocalTime workTime;

但是在运行过程中,发现报错,原因是EasyExcel并没有为LocalTime类型设置默认的转换工具类,所以利用步骤3的工具类,改一下泛型,自定义了LocalTime的Converter

java 复制代码
public class DateConverter implements Converter<LocalTime> {

    private static final String HH_MM_SS = "HH:mm";

    @Override  
    public Class<Date> supportJavaTypeKey(){ return Date.class; }

    @Override  
    public WriteCellData<String> convertToExcelData(WriteConverterContext<LocalTime> context) throws Exception {  
    LocalTime time = context.getValue();  
    if (time == null){ return null; }
    return new WriteCellData<>(time.toString());  
    }  
}

并在workTime变量上的注解上增加Converter类,实测,无效,情况依旧; 此时,发现问题变成了一个棘手的问题,设置成Date有年月日,设置成时间格式,最终还是会格式化成文本,并没有解决根本问题。然后尝试用注解,Converter,Handler,和变量类型,随意组合进行尝试,都无果。

5. 偶然发现的Excel角落的值

随后仔细研究了下目标成果文件,偶然发现真实情况下,记录为08:00的单元格,实际上存储的内容为数值0.333333333333,记录为18:00的单元格,实际存储内容为数值0.75。

看到这两个数字,想到了什么? 08点对应这一天24小时的1/3,18点对应这一天的3/4, 于是了解到excel在处理日期时间类型的时候,是按照double类型处理的,一天就对应这自然数1, 时分秒就对应着小数部分。

6. 再次改变DTO成员变量类型

于是回到修改Date类型那一步,workTime为java.util.Date类型,加上handler处理类,生成出来的内容如图:

发现了什么?是不是小数位一样,整数位是多出来的年月日,如何把这部去掉,又如何确定要去掉的数值大小呢? 想象一下,写入的日期是固定的某天,比如1970-01-01,那么生成出来的数字为:

25600.75 这么看是不是明显些,只要把25600减去就可以了,有同学问年月日都设置成0可以吗?不可以! 都置为0生成出来就是错码 ##############

7. 加工处理Handler

加工处理类hanler,减去对应的值

java 复制代码
public class ExcelCellWriteHandler implements CellWriteHandler {

@Override  
public void afterCellDispose(CellWriteHandlerContext context) {  
    // 3.0 设置单元格为文本  
    Workbook workbook = context.getWriteWorkbookHolder().getWorkbook();  
    if(context.getColumnIndex() >0 && context.getRowIndex() > 0){  
        Cell cell = context.getCell();  
        CellType cellType = cell.getCellType();  
        if (CellType.NUMERIC.equals(cellType)) {  
        double numericCellValue = cell.getNumericCellValue();  
            if (numericCellValue > 0.00) { 
            cell.setCellValue(numericCellValue - 25600);
            }
        }

        WriteCellData<?> cellData = context.getFirstCellData();  
        WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();  
        DataFormatData dataFormatData = new DataFormatData();  
        //设置什么类型的格式,可以搜索easyExcel的格式代表的序号去设置(20为 "H:mm" 格式)  
        dataFormatData.setIndex((short) 20);  
        writeCellStyle.setDataFormatData(dataFormatData);  
        cellData.setWriteCellStyle(writeCellStyle);  
    }
}
}

即在处理单元格时,判断是否是数值类型,且大于0,如果是就减去25600,剩下小数位,就是需要的时间。

8. 问题解决

问题至此解决;通过这个问题总结到: 1.Excel处理日期时间类是通过Double类型数值来存储记录的,只是展示成了时间格式; 2.通过代码生成的表格,特殊的单元格格式需要特殊处理,否则生成的内容都是文本格式,即使改变了单元格格式,也需要手动触发。所以,时间类型就需要用时间类来处理,剩下的就是后期加工的事儿;

相关推荐
鬼火儿2 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin2 小时前
缓存三大问题及解决方案
redis·后端·缓存
间彧4 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧4 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧4 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧4 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧4 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧4 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧4 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端
brzhang5 小时前
读懂 MiniMax Agent 的设计逻辑,然后我复刻了一个MiniMax Agent
前端·后端·架构