Apache POI 无法从 Excel 个单元格中正确读取秒数

Apache POI doesn't correctly read seconds from Excel cells

我目前正在调试别人的代码库。目的是将数据从 Excel 文件导入数据库。 excel 文件中的每一行在第 0 列中包含一个时间戳,在其他列中包含一些标签值。

时间戳包含年、月、day_of_month、时、分、秒。要解析 excel 文件并读取单个单元格,使用以下 APIs 和代码:

import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;

...

// some loop

   LocalDateTime dateCellValue = cell.getLocalDateTimeCellValue();

日期解析在大多数情况下都能正常工作,但我发现,这并不适用于所有情况。问题是,Java 有时会将时间戳的秒数延长纳秒。例如,时间戳“12.09.2018 12:39:11”被 Java 解释为“2018-09-12T12:39:10.995”。问题直接转化为数据库:文件中的第 11 秒被保存为数据库中的第 10 秒(因此纳秒工件消失了)。

为了了解问题的原因,我阅读了以下文档:https://github.com/apache/poi/blob/trunk/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java

由此我得出结论,我的问题的原因是舍入错误:API 将每个日期值解释为双精度值。在 Excel 中,您可以通过转换日期时间值来查看此双精度值,例如转换为文本或数字;例如“20.03.2019 08:36:39”变为“43544,3587847222”。因此,由于某些值的数据类型,四舍五入是不准确的,因此 Java 无法正确解析该值。

我的问题是,如何快速准确地解决此类问题。基本上,我考虑了两种可能性:

  1. 我可以通过 DateFormatter 来解析日期,而不是使用 Apache POI。然而,这有一个缺点,即用户定义的日期总是需要采用相同的格式,而目前它们不是。
  2. 在代码中,我可以尝试四舍五入以更正秒值并削减纳秒伪影。但是,我不确定,如果我总是需要舍入到上面的下一个第二个值(如上面所示的示例)或者是否存在我需要舍入到下一个的情况(例如低于 0.5 的纳秒值)下面的第二个值。我的问题是,关于舍入误差,我不完全理解程序行为背后的确切系统。

有人有什么建议吗?非常感谢您的帮助!

编辑: 问题的原因是,纳秒已经在 Excel 表中,但肉眼无法识别,因为相关数据类型没有显示它们。

我无法重现该问题。如果 apache poi 得到 LocalDateTime 2018-09-12T12:39:10.995 则 Excel 单元格存储了确切的日期时间。当然 Excel 可能不会准确显示它,因为日期格式已经四舍五入了。例如,日期格式 DD.MM.YYYY hh:mm:ss 将显示 12.09.2018 12:39:11 表示 2018-09-12T12:39:10.995。但存储的是确切的日期时间。

但是如果需要得到 LocalDateTime 的精确度仅为秒,则可以增加 0.5 秒(千分之 500 秒),然后截断为秒。该方法会将 LocalDateTime 舍入到秒。

LocalDateTime dateCellValue = cell.getLocalDateTimeCellValue(); //got directly from Excel
dateCellValue  = dateCellValue.plusNanos(500000000).truncatedTo(ChronoUnit.SECONDS); //round to seconds

完整示例:

Excel sheet 看起来像:

此处 B 列中的单元格值是日期时间值。单元格编号格式为 TT.MM.YYYY hh:mm:ss.000.

代码:

import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.*;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

import java.io.FileInputStream;

class ExcelReadLocalDateTime {

 public static void main(String[] args) throws Exception {
  //Workbook workbook = WorkbookFactory.create(new FileInputStream("Workbook.xls")); String filePath = "WorkbookNew.xls";
  Workbook workbook = WorkbookFactory.create(new FileInputStream("Workbook.xlsx")); String filePath = "WorkbookNew.xlsx";

  Sheet sheet = workbook.getSheetAt(0);

  for (Row row : sheet) {
   for (Cell cell : row) {
    switch (cell.getCellType()) {
     case STRING:
      System.out.println(cell.getRichStringCellValue().getString());
      break;
     case NUMERIC:
      if (DateUtil.isCellDateFormatted(cell)) {
       LocalDateTime dateCellValue = cell.getLocalDateTimeCellValue(); //got directly from Excel
       System.out.println(dateCellValue);
       dateCellValue  = dateCellValue.plusNanos(500000000).truncatedTo(ChronoUnit.SECONDS); //round to seconds
       System.out.println(dateCellValue);
      } else {
       System.out.println(cell.getNumericCellValue());
      }
      break;    
     default:
      System.out.println();
    }
   }
  }

  workbook.close();
 }
}

结果:

Text
DateTime
DT 1
2018-09-12T12:39:10
2018-09-12T12:39:10
DT 2
2018-09-12T12:39:10.123
2018-09-12T12:39:10
DT 3
2018-09-12T12:39:10.245
2018-09-12T12:39:10
DT 4
2018-09-12T12:39:10.370
2018-09-12T12:39:10
DT 5
2018-09-12T12:39:10.495
2018-09-12T12:39:10
DT 6
2018-09-12T12:39:10.500
2018-09-12T12:39:11
DT 7
2018-09-12T12:39:10.620
2018-09-12T12:39:11
DT 8
2018-09-12T12:39:10.745
2018-09-12T12:39:11
DT 9
2018-09-12T12:39:10.870
2018-09-12T12:39:11
DT 10
2018-09-12T12:39:10.995
2018-09-12T12:39:11