杰瑞科技汇

Java Excel日期格式怎么设置?

下面我将从核心原理、常用库(Apache POI、EasyExcel)的操作方法以及常见问题三个方面,为你详细讲解 Java 如何处理 Excel 的日期格式。


核心原理:Excel 和 Java 的日期本质

理解这一点是解决所有日期问题的关键。

Excel 如何存储日期

  • 底层存储:Excel 将日期和时间存储为一个双精度浮点数
  • 整数部分:代表自 1900年1月1日 以来的天数。
    • 1 代表 1900/1/2,2 代表 1900/1/3,...,44562 代表 2025/1/1。
  • 小数部分:代表一天中的时间。
    • 5 代表中午 12:00,25 代表早上 6:00。
  • 著名的 Bug:Excel 的日期系统有一个著名的 bug:它错误地认为 1900年是闰年1900/2/29(这个日期不存在)在 Excel 中被存储为 0,这在处理跨 1900 年的日期时需要注意,但现代应用通常不会涉及。

Java 如何存储日期

  • java.util.Date:一个传统的类,同时包含日期和时间信息,内部用一个 long 类型存储自 1970年1月1日 00:00:00 GMT 以来的毫秒数。
  • java.time 包 (Java 8+):现代 Java 日期时间 API,提供了更清晰、更安全的类,如 LocalDate (仅日期), LocalDateTime (日期和时间), ZonedDateTime (带时区的日期时间)。

两者之间的转换

当使用 Java 库(如 Apache POI)读取 Excel 单元格时,如果单元格被格式化为日期,库会:

  1. 从 Excel 文件中读取那个双精度浮点数
  2. 将这个浮点数转换为 Java 可以理解的日期对象。

转换的关键公式Java 日期 = Excel 日期值 - Excel 基准日期的天数 + Java 基准日期的毫秒数

  • Excel 基准日期: 1900-01-01
  • Java 基准日期: 1970-01-01

POI 等库已经帮我们完成了这个复杂的转换工作,我们只需要正确地调用 API 即可。


使用 Apache POI 处理日期格式

Apache POI 是最流行的 Java 操作 Office 文件的库。

读取 Excel 日期

核心步骤

  1. 判断单元格类型是否为 CellType.NUMERIC
  2. 使用 DateUtil.isCellDateFormatted(cell) 方法判断这个数字是否被格式化为日期。
  3. 如果是,直接使用 cell.getDateCellValue() 获取 java.util.Date 对象。
  4. 如果不是,但你知道它应该是日期,你需要手动进行转换(使用 DateUtil.getJavaDate())。

代码示例

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class PoiDateReader {
    public static void main(String[] args) {
        String filePath = "your_excel_file.xlsx";
        try (FileInputStream fis = new FileInputStream(filePath);
             Workbook workbook = new XSSFWorkbook(fis)) {
            Sheet sheet = workbook.getSheetAt(0);
            Row row = sheet.getRow(0); // 假设日期在第一行
            Cell cell = row.getCell(0);
            // 1. 检查单元格类型
            if (cell.getCellType() == CellType.NUMERIC) {
                // 2. 检查是否是格式化的日期
                if (DateUtil.isCellDateFormatted(cell)) {
                    // 3. 直接获取 Date 对象
                    Date date = cell.getDateCellValue();
                    System.out.println("直接读取的 Date 对象: " + date);
                    // 4. 格式化输出为你想要的字符串格式
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    String formattedDate = sdf.format(date);
                    System.out.println("格式化后的日期字符串: " + formattedDate);
                } else {
                    // 如果单元格是数字但未格式化为日期,44562
                    double numericValue = cell.getNumericCellValue();
                    System.out.println("单元格是纯数字,未格式化: " + numericValue);
                    // 手动将其转换为 Java 日期
                    Date date = DateUtil.getJavaDate(numericValue);
                    System.out.println("手动转换后的 Date 对象: " + date);
                }
            } else {
                System.out.println("单元格不是数字类型,无法作为日期处理。");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

写入 Excel 日期

写入日期时,你需要做两件事:

  1. 设置单元格的值(setCellValue)。
  2. 为单元格应用日期格式(CellStyle)。

代码示例

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class PoiDateWriter {
    public static void main(String[] args) {
        String filePath = "output_excel_file.xlsx";
        // 1. 创建工作簿和工作表
        Workbook workbook = new XSSFWorkbook();
        Sheet sheet = workbook.createSheet("日期示例");
        // 2. 创建日期格式
       CellStyle dateCellStyle = workbook.createCellStyle();
        CreationHelper createHelper = workbook.getCreationHelper();
        // 设置日期格式,"yyyy-MM-dd"
        dateCellStyle.setDataFormat(createHelper.createDataFormat().getFormat("yyyy-MM-dd"));
        // 3. 创建行和单元格
        Row row = sheet.createRow(0);
        Cell cell = row.createCell(0);
        // 4. 设置单元格值为当前日期
        Date currentDate = new Date();
        cell.setCellValue(currentDate);
        // 5. 应用日期格式到单元格
        cell.setCellStyle(dateCellStyle);
        // 6. 写入文件
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            workbook.write(fos);
            System.out.println("Excel 文件已成功创建,并写入了日期。");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                workbook.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

使用 EasyExcel 处理日期格式

EasyExcel 是阿里巴巴开源的库,以其“简单、节省内存”著称,尤其适合处理大数据量的 Excel 文件。

读取 Excel 日期

EasyExcel 的读取方式是基于模型的(Model-based),你需要定义一个与 Excel 行对应的 Java 实体类。

步骤

  1. 定义实体类:在需要映射日期的字段上使用 @DateTimeFormat 注解来指定 Excel 中的日期格式。
  2. 创建监听器:创建一个 AnalysisEventListener 来处理读取到的数据。
  3. 读取文件:调用 EasyExcel.read() 方法。

代码示例

实体类 DateModel.java

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
public class DateModel {
    // @ExcelProperty 指定列名
    // @DateTimeFormat 指定 Excel 中该列的日期格式
    @ExcelProperty("订单日期")
    @DateTimeFormat("yyyy-MM-dd HH:mm:ss") // 必须与 Excel 文件中的格式匹配
    private Date orderDate;
    // Getters and Setters
    public Date getOrderDate() {
        return orderDate;
    }
    public void setOrderDate(Date orderDate) {
        this.orderDate = orderDate;
    }
    @Override
    public String toString() {
        return "DateModel{" +
                "orderDate=" + orderDate +
                '}';
    }
}

读取主程序

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.ArrayList;
import java.util.List;
public class EasyExcelDateReader {
    public static void main(String[] args) {
        String filePath = "your_excel_file.xlsx";
        // 使用监听器来读取数据
        EasyExcel.read(filePath, DateModel.class, new AnalysisEventListener<DateModel>() {
            // 存储读取的数据
            private List<DateModel> dataList = new ArrayList<>();
            @Override
            public void invoke(DateModel data, AnalysisContext context) {
                // 每解析一行,就会调用一次这个方法
                dataList.add(data);
                System.out.println("解析到一条数据: " + data);
            }
            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                // 所有数据解析完成后的操作
                System.out.println("所有数据解析完成!");
                System.out.println("总共解析了 " + dataList.size() + " 条数据。");
            }
        }).sheet().doRead();
    }
}

写入 Excel 日期

写入时,同样使用实体类,但这次用 @WriteDateTimeFormat 注解。

实体类 DateModelForWrite.java

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.WriteDateTimeFormat;
public class DateModelForWrite {
    @ExcelProperty("创建时间")
    // 指定写入 Excel 时的日期格式
    @WriteDateTimeFormat("yyyy年MM月dd日")
    private Date createTime;
    // Getters and Setters
    public Date getCreateTime() {
        return createTime;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

写入主程序

import com.alibaba.excel.EasyExcel;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class EasyExcelDateWriter {
    public static void main(String[] args) {
        String filePath = "output_easyexcel_file.xlsx";
        // 1. 准备数据
        List<DateModelForWrite> dataList = new ArrayList<>();
        DateModelForWrite data1 = new DateModelForWrite();
        data1.setCreateTime(new Date());
        dataList.add(data1);
        // 2. 写入 Excel
        EasyExcel.write(filePath, DateModelForWrite.class).sheet("日期写入").doWrite(dataList);
        System.out.println("EasyExcel 文件已成功创建,并写入了日期。");
    }
}

常见问题与最佳实践

读取的日期是 Double 类型,而不是 Date

  • 原因:最常见的原因是 DateUtil.isCellDateFormatted(cell) 返回了 false,这可能是因为:
    • Excel 单元格虽然输入了日期,但被你手动改成了“常规”或“数字”格式。
    • 单元格是通过公式生成的,且公式结果没有明确设置为日期格式。
  • 解决方案
    1. 检查 Excel 文件,确保单元格格式被设置为“日期”。
    2. 在代码中,如果确定单元格应该是日期,可以强制使用 DateUtil.getJavaDate(cell.getNumericCellValue()) 进行转换。

读取的日期不正确(相差 4 年或 1 天)

  • 原因

    • 相差 4 年:这是因为 POI 默认使用 1900 年日期系统,而你的 Excel 文件可能使用了 1904 年日期系统(在 Excel for Mac 中很常见),1904 年系统以 1904-01-01 为基准,可以避免 1900 年的闰年问题。
    • 相差 1 天:通常是 1900 年闰年 bug 导致的。
  • 解决方案

    • 在读取 Workbook 时,明确指定日期系统。
      // 对于 .xls 文件
      Workbook workbook = new HSSFWorkbook(fis);
      workbook.setMissingCellPolicy(Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
      // 设置使用 1904 日期系统
      workbook.set1904(false); // false 是 1900 系统,true 是 1904 系统

    // 对于 .xlsx 文件 Workbook workbook = new XSSFWorkbook(fis); // XSSF 没有直接 set1904 的方法,但可以通过底层 OOXML 实现 // ((XSSFWorkbook) workbook).getProperties().getCoreProperties().setModified(new Date()); // 这不是直接方法 // 更简单的方法是检查文件属性,或者在读取前判断,然后使用 POI 的特定 API。 // 如果文件是 .xlsx,且日期不正确,多半是 1904 系统。 // 可以通过以下方式强制设置 if (workbook instanceof XSSFWorkbook) { ((XSSFWorkbook) workbook).getProperties().getExtendedProperties().setApplication("Microsoft Excel"); // 这是一个间接的方法,有时能解决问题,最可靠的方式是提前知道文件使用哪种系统。 }

    
    *   **最佳实践**:在读取 Excel 前,最好能确认文件的日期系统设置,如果无法确认,并且遇到日期错乱,可以尝试切换 `set1904` 的值。

最佳实践总结

  1. 优先使用 Java 8 的 java.time:如果你使用的是 Java 8 或更高版本,强烈建议使用 LocalDateLocalDateTime 代替旧的 java.util.Date

    • 读取转换
      // 假设 cell 是一个日期格式的单元格
      Date date = cell.getDateCellValue();
      LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
    • 写入转换
      LocalDate localDate = LocalDate.now();
      Date date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
      cell.setCellValue(date);
  2. 明确格式:无论是读取还是写入,都明确指定日期格式(如 yyyy-MM-dd),避免因系统默认不同导致的问题。

  3. 检查文件:遇到问题时,首先用 Excel 软件打开文件,检查单元格的格式设置是否正确。

  4. 选择合适的库

    • Apache POI:功能最全面,支持所有 Office 版本,适合复杂的操作。
    • EasyExcel:API 更简洁,内存占用低,适合读取大数据量的 Excel 文件。
分享:
扫描分享到社交APP
上一篇
下一篇