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

相关推荐
Rain5097 分钟前
2.1 Nest.js 项目初始化与模块化架构
开发语言·前端·javascript·后端·架构·数据分析·node.js
cjp56011 分钟前
009. ASP.NET WEB API 用户关联esp32设备
前端·后端·asp.net
贺国亚17 分钟前
Text-to-SQL与Analytics-Agent
后端
一只叫煤球的猫35 分钟前
ThreadForge 源码解读二:一个 Task 从 submit 到完成,内部到底发生了什么?
java·后端·面试
苏三说技术1 小时前
AgentScope Java 2.0 正式发布了!
后端
ping某1 小时前
一个“日志备份”需求,为什么会牵出整个 Linux 日志系统?
后端·架构
血小溅1 小时前
Spring AI 对 Skill/MCP 的支持全景整理
后端
晓杰'2 小时前
从0到1实现Balatro游戏后端(8):Skip Blind与Tag奖励机制设计与实现
后端·websocket·typescript·项目实战·nestjs·状态管理·游戏服务器
叫我:松哥2 小时前
基于Flask框架的校园二手书籍交易平台,注重校园场景的特殊需求,通过学号认证保障用户真实性
后端·python·sqlite·flask·bootstrap
终将老去的穷苦程序员2 小时前
基于SpringBoot的餐饮管理系统
java·spring boot·后端