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