[alibaba/easyexcel]读取Time列时获取到的值比文件值少5分多钟

2024-02-21 817 views
7
触发场景描述

读取Excel,内容中有Time列,内容为10:05:35 , 但实际读到的为9:59:52,比实际文件中的时间少了5分多 EasyExcel版本为3.3.2

触发Bug的代码
 package com.di1shuai.easyexcel;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;

import java.io.InputStream;
import java.util.LinkedHashMap;

/**
 * @author shea
 * @since 2023/6/25
 */
public class Main {

    public static void main(String[] args) {
        String fileName = "time_problem.xlsx";
        InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
        int skipHeaderNumber = 1;
        String sheetName = null;
        EasyExcel.read(
                        in,
                        null,
                        new ReadListener() {

                            @Override
                            public void invoke(Object data, AnalysisContext context) {
                                LinkedHashMap<Integer, Object> dataMap = (LinkedHashMap) data;
                                dataMap.entrySet()
                                        .forEach(
                                                dataE -> {
                                                    System.out.println(dataE.getKey() + " -> " + dataE.getValue());
                                                });
                            }

                            @Override
                            public void doAfterAllAnalysed(AnalysisContext context) {
                                System.out.println("读取成功");
                            }
                        })
                .sheet(sheetName)
                .headRowNumber((int) skipHeaderNumber)
                .doRead();

    }

}

输出为

0 -> 3
1 -> 2
2 -> 3
3 -> 4
4 -> 5
5 -> 9223372036854780
6 -> -1.123456
7 -> -2.1245
8 -> 6.123456
9 -> 9:59:52
10 -> 2022/11/28
11 -> 2022/11/28 10:05:45
12 -> 2022/11/28 10:05:48
13 -> a
14 -> bdds
15 -> 长文本数据0-65 535 bytes
16 -> 中等文本数据0-16 777 215 bytes
17 -> 极大文本数据0-4 294 967 295 bytesLONGTEXT
0 -> 3
1 -> 2
2 -> 3
3 -> 4
4 -> 5
5 -> 9223372036854780
6 -> -1.123456
7 -> -2.1245
8 -> 6.123456
9 -> 9:59:52
10 -> 2022/11/28
11 -> 2022/11/28 10:05:45
12 -> 2022/11/28 10:05:48
13 -> a
14 -> bdds
15 -> 长文本数据0-65 535 bytes
16 -> 中等文本数据0-16 777 215 bytes
17 -> 极大文本数据0-4 294 967 295 bytesLONGTEXT
读取成功
提示的异常或者没有达到的效果

没有抛出异常,期望能够正确获取到Excel中的时间内容,如10:05:35 相关代码已贴在GIthub -> https://github.com/BestBurning/EasyExcelProblem clone后为Maven项目,仅依赖了EasyExcel,Excel也放在了Resource目录,可直接运行

相关Excel文件已贴在Github -> https://github.com/BestBurning/EasyExcelProblem/blob/main/src/main/resources/time_problem.xlsx

回答

7

感谢这么详细的issue

解决方案: 使用实体类而不是Map的方式 指定你的列为JDK8提供的LocalDateTime作为属性

public class Bean {

    @ExcelProperty("time_field")
    private LocalDateTime time;

    public LocalDateTime getTime() {
        return time;
    }

    public void setTime(LocalDateTime time) {
        this.time = time;
    }
}

public static void main(String[] args) {
        String fileName = "time_problem.xlsx";
        InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
        int skipHeaderNumber = 1;
        EasyExcel.read(in, Bean.class, new ReadListener<Bean>() {
                    @Override
                    public void invoke(Bean data, AnalysisContext context) {
                        System.out.println(data.getTime());
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext context) {
                        System.out.println("读取成功");
                    }
                })
                .sheet()
                .headRowNumber(skipHeaderNumber)
                .doRead();

    }

原因是JAVA的Date和Excel的起始时间不同,当 Excel 中的日期为 1899 年时,它实际上是被存储为数字 0。然而,在 Java 中,将数字 0 解释为 1970 年 1 月 1 日。因此,当使用 java.util.Date 类读取 Excel 中的日期时,日期 0 被解释为 1899 年而不是 1970 年。 所以当你不指定日期的时候 时间是可能会错乱的。 当然,如果你指定日期
比如 变成这样

image

结果就是正常的了

3

感谢大佬的回答,但是目前的场景确实不可能有实体类,甚至不可能有template,是个纯动态的场景(例如SeaTunnel这种数据集成工具,来做各种数据的Excel的接入),并且Time列就是仅包含Time,无法指定年份,这样的话,使用EasyExcel又要如何兼容呢

7
  1. 能否使用语义化字符串来表示 而不是时间类型
  2. 可以自定义解析器注入,我写了个demo你可以参考一下
public class MyStringNumberConverter extends StringNumberConverter {

    private static final String timePattern = "h:mm:ss";

    private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(timePattern);

    @Override
    public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
                                    GlobalConfiguration globalConfiguration) {
        // If there are "DateTimeFormat", read as date
        if (contentProperty != null && contentProperty.getDateTimeFormatProperty() != null) {
            return DateUtils.format(cellData.getNumberValue(),
                    contentProperty.getDateTimeFormatProperty().getUse1904windowing(),
                    contentProperty.getDateTimeFormatProperty().getFormat());
        }
        // If there are "NumberFormat", read as number
        if (contentProperty != null && contentProperty.getNumberFormatProperty() != null) {
            return NumberUtils.format(cellData.getNumberValue(), contentProperty);
        }
        // Excel defines formatting
        boolean hasDataFormatData = cellData.getDataFormatData() != null
                && cellData.getDataFormatData().getIndex() != null && !StringUtils.isEmpty(
                cellData.getDataFormatData().getFormat());

        if (hasDataFormatData) {
            if(Objects.equals(cellData.getDataFormatData().getFormat(),timePattern)){
                LocalDateTime localDateTime = DateUtils.getLocalDateTime(cellData.getNumberValue().doubleValue(), false);
                return dateTimeFormatter.format(localDateTime);
            }
            return NumberDataFormatterUtils.format(cellData.getNumberValue(),
                    cellData.getDataFormatData().getIndex(), cellData.getDataFormatData().getFormat(), globalConfiguration);
        }
        // Default conversion number
        return NumberUtils.format(cellData.getNumberValue(), contentProperty);
    }
}

// 下面是注册的伪代码
public static void main(String[] args){
 EasyExcel.read().registerConverter(new MyStringNumberConverter()) .doRead();
}