杰瑞科技汇

Excel日期导入Java,如何正确转换格式?

第一步:准备工作(添加依赖)

你需要一个库来读取 Excel 文件,目前最流行、功能最强大的库是 Apache POI

如果你使用 Maven,在 pom.xml 中添加以下依赖:

<dependencies>
    <!-- Apache POI 核心依赖 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.3</version>
    </dependency>
    <!-- 用于处理 .xlsx 格式 (Office 2007 及以后) -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.3</version>
    </dependency>
    <!-- 用于处理 .xls 格式 (Office 97-2003) -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.3</version>
    </dependency>
    <!-- 为了方便地处理日期,我们使用 Java 8 的 java.time 包 -->
    <!-- 如果你使用的是旧版 Java (如 Java 7),则不需要这个,但会麻烦很多 -->
</dependencies>

第二步:理解 Excel 日期存储的原理

这是最关键的一步。

  1. 数字形式:在 Excel 内部,日期和时间被存储为一个序列号(Serial Number),这个数字代表从 1900-01-00 (这是一个历史遗留问题,Excel 认为 1900 年是闰年,2 月有 29 天) 开始算起的天数。

    • 2025-10-27 在 Excel 中可能存储为 45219
    • 时间部分是小数,12:00:005 天。
    • 2025-10-27 12:00:00 可能存储为 5
  2. 单元格类型:当你在 Excel 中设置一个单元格为“日期”格式时,POI 读取到的单元格类型是 CellType.NUMERIC,POI 提供了一个方法来判断这个数字是否应该被解释为日期。


第三步:编写 Java 代码读取 Excel 日期

下面是一个完整的、可运行的示例代码,它会读取一个 Excel 文件,并正确地将日期列转换为 Java 对象。

示例 Excel 文件 (dates.xlsx)

假设你的 Excel 文件内容如下:

姓名 出生日期 注册时间
张三 1990-05-15 2025-01-10 09:30
李四 1985-11-20 2025-12-25 15:45
王五 1995-08-01 2025-10-27 00:00

Java 代码实现

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
public class ExcelDateReader {
    public static void main(String[] args) {
        // 1. 定义 Excel 文件路径
        String excelFilePath = "path/to/your/dates.xlsx"; // 请替换为你的文件路径
        try (FileInputStream fis = new FileInputStream(new File(excelFilePath));
             Workbook workbook = new XSSFWorkbook(fis)) { // 使用 XSSFWorkbook 处理 .xlsx 文件
            // 2. 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);
            // 3. 遍历每一行(跳过表头)
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (row == null) {
                    continue;
                }
                // 读取姓名(字符串)
                Cell nameCell = row.getCell(0);
                String name = nameCell.getStringCellValue();
                // 读取出生日期(日期)
                Cell birthdayCell = row.getCell(1);
                Date birthday = parseExcelDate(birthdayCell);
                // 读取注册时间(日期+时间)
                Cell registerTimeCell = row.getCell(2);
                Date registerTime = parseExcelDate(registerTimeCell);
                // 4. 打印结果,验证日期是否正确
                // 使用 SimpleDateFormat 来格式化日期,方便查看
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                System.out.println("姓名: " + name);
                System.out.println("出生日期 (Date): " + birthday);
                System.out.println("出生日期 (格式化): " + sdf.format(birthday));
                System.out.println("注册时间 (Date): " + registerTime);
                System.out.println("注册时间 (格式化): " + sdf.format(registerTime));
                System.out.println("----------------------");
                // --- 推荐做法:使用 Java 8 的 java.time API ---
                // 将 java.util.Date 转换为 java.time.LocalDate (仅日期)
                LocalDate localBirthday = birthday.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                System.out.println("出生日期 (LocalDate): " + localBirthday);
                // 将 java.util.Date 转换为 java.time.LocalDateTime (日期+时间)
                LocalDateTime localRegisterTime = registerTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
                System.out.println("注册时间 (LocalDateTime): " + localRegisterTime);
                System.out.println("=======================");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * 核心方法:解析 Excel 单元格中的日期
     * @param cell Excel 单元格对象
     * @return 解析后的 java.util.Date 对象
     */
    private static Date parseExcelDate(Cell cell) {
        if (cell == null) {
            return null;
        }
        // 检查单元格类型是否为数字,并且是日期格式
        if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {
            // 如果是日期格式,直接获取日期值
            return cell.getDateCellValue();
        }
        // 如果不是日期格式,但单元格类型是数字,说明它存储了日期的序列号
        // 这种情况通常发生在 Excel 单元格被设置为 "常规" 格式,但实际输入了日期
        if (cell.getCellType() == CellType.NUMERIC) {
            // 使用 DateUtil.getJavaDate() 将序列号转换为 Java 日期
            // 第二个参数 false 表示不使用 1900 闰年 bug 的修正(Excel 默认有 bug)
            // 对于大多数现代 Excel 文件,使用 true 是安全的
            return DateUtil.getJavaDate(cell.getNumericCellValue(), true);
        }
        // 如果是字符串格式,可能用户手动输入了 "yyyy-MM-dd" 这样的文本
        if (cell.getCellType() == CellType.STRING) {
            try {
                // 尝试按常见格式解析字符串
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
                return sdf.parse(cell.getStringCellValue());
            } catch (Exception e) {
                System.err.println("无法解析字符串格式的日期: " + cell.getStringCellValue());
                return null;
            }
        }
        // 其他类型(布尔、公式等)不是日期,返回 null
        return null;
    }
}

第四步:代码详解与关键点

核心解析方法 parseExcelDate(Cell cell)

这个方法是整个流程的灵魂,它处理了多种可能性:

  • DateUtil.isCellDateFormatted(cell):这是 POI 提供的最可靠的判断方法,它会检查 Excel 单元格是否被明确地格式化为日期或时间格式(在 Excel 中右键单元格 -> 设置单元格格式 -> 日期),如果返回 true,直接用 cell.getDateCellValue() 即可。

  • DateUtil.getJavaDate(double serial, boolean use1904windowing):当 DateUtil.isCellDateFormatted(cell) 返回 false,但单元格类型是 NUMERIC 时,我们怀疑它可能是“伪装”成数字的日期序列号。

    • serial: Excel 里的那个数字(如 45219)。
    • use1904windowing: 这是一个非常重要的参数。
      • true: 表示从 1904-01-01 开始计算,主要用于 Mac Excel。
      • false: 表示从 1900-01-01 开始计算(这是 Windows Excel 的默认行为,也是最常见的)。
    • 对于绝大多数从 Windows Excel 导出的文件,使用 true 是最安全的选择,因为它会修正 Excel 1900 年闰年的 bug。
  • 处理字符串:有时候用户可能会把单元格格式设置为“文本”,然后输入 1990-05-15,这时 cell.getCellType() 会是 CellType.STRING,代码里提供了一个简单的字符串解析作为兜底方案,但实际项目中可能需要更健壮的日期解析逻辑。

推荐使用 java.time API

java.util.Date 是一个遗留类,有很多设计缺陷,自 Java 8 起,官方推荐使用 java.time 包下的类。

  • LocalDate: 表示一个日期(年、月、日),没有时间部分,非常适合像“出生日期”这样的场景。
  • LocalDateTime: 表示一个日期和时间(年、月、日、时、分、秒),非常适合像“注册时间”这样的场景。

转换代码:

// Date -> LocalDateTime
Date date = ...; // 从 Excel 解析出的 Date
ZoneId zoneId = ZoneId.systemDefault(); // 获取系统默认时区
LocalDateTime localDateTime = date.toInstant().atZone(zoneId).toLocalDateTime();
// Date -> LocalDate
LocalDate localDate = date.toInstant().atZone(zoneId).toLocalDate();

第五步:处理不同 Excel 版本

  • .xlsx (Office 2007+): 使用 XSSFWorkbook,这是目前的主流格式。
  • .xls (Office 97-2003): 使用 HSSFWorkbook,用法几乎完全一样,只是在创建 Workbook 对象时换一个类。
// 对于 .xls 文件
Workbook workbook = new HSSFWorkbook(fis);

总结与最佳实践

  1. 首选依赖:使用 Apache POI。
  2. 核心判断:使用 DateUtil.isCellDateFormatted(cell) 来判断一个数字单元格是否是真正的日期。
  3. 数字转换:对于被识别为日期的数字,用 cell.getDateCellValue(),对于未被识别但可能是日期的数字,用 DateUtil.getJavaCellValue(num, true)
  4. 数据类型:在 Java 中,优先使用 java.time.LocalDatejava.time.LocalDateTime 来处理日期和时间,而不是 java.util.Date
  5. 健壮性:编写一个通用的 parseExcelDate 方法,能处理日期格式、数字序列和文本字符串等多种情况,让你的代码更健壮。

遵循以上步骤,你就可以稳定、准确地从 Excel 文件中导入日期数据了。

分享:
扫描分享到社交APP
上一篇
下一篇