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

相关推荐
2401_8576100315 分钟前
Spring Boot框架:电商系统的技术优势
java·spring boot·后端
杨哥带你写代码2 小时前
网上商城系统:Spring Boot框架的实现
java·spring boot·后端
camellias_2 小时前
SpringBoot(二十一)SpringBoot自定义CURL请求类
java·spring boot·后端
背水3 小时前
初识Spring
java·后端·spring
晴天飛 雪3 小时前
Spring Boot MySQL 分库分表
spring boot·后端·mysql
weixin_537590453 小时前
《Spring boot从入门到实战》第七章习题答案
数据库·spring boot·后端
AskHarries3 小时前
Spring Cloud Gateway快速入门Demo
java·后端·spring cloud
Qi妙代码4 小时前
MyBatisPlus(Spring Boot版)的基本使用
java·spring boot·后端
宇宙超级勇猛无敌暴龙战神4 小时前
Springboot整合xxl-job
java·spring boot·后端·xxl-job·定时任务