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.通过代码生成的表格,特殊的单元格格式需要特殊处理,否则生成的内容都是文本格式,即使改变了单元格格式,也需要手动触发。所以,时间类型就需要用时间类来处理,剩下的就是后期加工的事儿;

相关推荐
sunnyday04263 分钟前
从混乱到清晰:Maven 依赖版本管理最佳实践
java·spring boot·后端·maven
雨中飘荡的记忆13 分钟前
Spring Test 从入门到实战
java·后端·spring
晨非辰27 分钟前
C++波澜壮阔40年|类和对象篇:拷贝构造与赋值重载的演进与实现
运维·开发语言·c++·人工智能·后端·python·深度学习
韩立学长1 小时前
基于Springboot琴行学生课程信息管理系统2gt392wb(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
XXOOXRT1 小时前
基于SpringBoot的用户登录
java·spring boot·后端
努力也学不会java1 小时前
【Spring Cloud】环境和工程基本搭建
java·人工智能·后端·spring·spring cloud·容器
源代码•宸1 小时前
Golang原理剖析(interface)
服务器·开发语言·后端·golang·interface·type·itab
burning_maple1 小时前
设计数据密集型应用阅读笔记
分布式·后端·中间件
橘橙黄又青2 小时前
Spring篇
java·后端·spring
hhzz2 小时前
Springboot项目中使用EasyPOI方式导出合同word文档
java·spring boot·后端·word·poi·easypoi