Excel 与数据

Excel 日期差了四年:先看工作簿日期系统

iso_dates 控制文件中的日期序列化方式,不会把内存中的 datetime 自动变成字符串。日期序列号需要使用工作簿的 epoch;这里对照两个日期系统并检查保存后的 XML 和回读值。

阅读文本 / Reading text
本文使用合成样本演示处理方法。代码已通过隔离环境中的示例检查;这不代表对所有真实文件或产品效果的保证。处理重要文件前,请保留原件,并按文中的边界与验收项复核。 这篇先解决哪一个问题 iso_dates 控制文件中的日期序列化方式,不会把内存中的 datetime 自动变成字符串。日期序列号需要使用工作簿的 epoch;这里对照两个日期系统并检查保存后的 XML 和回读值。 自包含的合成示例 依赖见导入列表。代码在内存中构造输入;运行记录单独提供,不代表对所有真实文件的保证。 from datetime import datetime from io import BytesIO from zipfile import ZipFile from openpyxl import Workbook, load_workbook from openpyxl.utils.datetime import from_excel, CALENDAR_MAC_1904, CALENDAR_WINDOWS_1900 assert from_excel(1, CALENDAR_MAC_1904) == datetime(1904, 1, 2) assert from_excel(1, CALENDAR_WINDOWS_1900) == datetime(1900, 1, 1) wb = Workbook() wb.epoch = CALENDAR_MAC_1904 wb.iso_dates = True value = datetime(2026, 9, 26, 12, 30) wb.active["A1"] = value assert wb.active["A1"].value == value out = BytesIO() wb.save(out) with ZipFile(BytesIO(out.getvalue())) as z: xml = z.read("xl/worksheets/sheet1.xml") assert b't="d"' in xml and b"2026-09-26T12:30:00" in xml restored = load_workbook(BytesIO(out.getvalue())) assert restored.active["A1"].value == value assert restored.epoch == CALENDAR_MAC_1904 Excel 日期差了四年:先看工作簿日期系统 当你发现导出的 Excel 日期整体偏移了 1462 天(约四年),或者单元格明明写着数字却怎么也无法格式化为日期时,问题通常不在数据本身,而在工作簿的“日期系统”设置上。XLSX 规范允许两种基准纪元:1900 年系统(Windows 默认)和 1904 年系统(早期 Mac 默认)。如果读取端假设了错误的纪元,所有序列号都会被错误解释。在动手写转换逻辑前,必须先确认 workbook.epoch 属性,这是解决此类偏差的唯一可靠起点。 1900 与 1904 纪元的本质差异 XLSX 文件中的日期本质上是一个浮点数序列号,其含义完全取决于工作簿绑定的日期系统。在 1900 系统中,序列号 1 代表 1900-01-01;而在 1904 系统中,序列号 0 才代表 1904-01-01。两者之间不仅存在 1462 天的固定偏移,还夹杂着一个著名的历史遗留 bug:1900 系统错误地将 1900 年视为闰年,导致 1900-02-29 这个不存在的日期在 Excel 中被合法化。 这意味着,如果你在处理 1900 年 3 月之前的历史数据,Python 的 datetime 模块无法直接表示 2 月 29 日这一天。根据 openpyxl 官方文档,为了兼容 Excel 的行为,库在读取 1900 系统下 1 月和 2 月的日期时会模拟这一异常,但 1900-02-29 本身无法转换为有效的 Python datetime 对象。对于绝大多数现代业务数据,我们只需关注如何正确识别当前工作簿使用的是哪种系统,并在跨平台交换文件时显式声明,避免接收方因默认设置不同而产生静默偏移。 合成示例:正确输出与边界陷阱 为了说明问题,我们构造一个合成场景:某财务系统导出的报表使用了 1904 日期系统,但下游 ETL 脚本默认按 1900 系统解析。 错误/边界输入及处理 :若输入序列号为 60 且工作簿为 1900 系统,这对应 Excel 中虚构的 1900-02-29。Python 原生 datetime 会抛出 ValueError 。此时不应强行转换,而应捕获异常并记录为原始数值或特殊标记,因为该日期在公历中不存在。另外,若传入带有时区信息的 datetime 对象尝试写入,openpyxl 不会自动转换时区,而是要求用户先剥离 tzinfo,否则可能写入错误或报错。Excel 本身不支持时区存储,所有时间均为“无感知”本地时间。 openpyxl 日期系统检测与安全转换代码 以下示例展示了如何在内存中安全地处理日期系统差异。请注意,此代码仅为逻辑演示,未执行实际文件读写,需在你自己锁定的依赖环境中运行(推荐 openpyxl >= 3.1.0)。示例不包含网络请求或文件覆盖操作,所有断言均基于合成数据。 验收标准与人工审查边界 观察现象 输入/边界条件 预期输出/处理决定 是否适用自动化 所有日期统一偏移1462天 wb.epoch与解析假设不一致 读取wb.epoch后重新转换序列号 是 1900-02-29出现在数据中 1900系统且序列号=60 标记为无效日期,保留原始序列号60 否,需人工确认业务含义 时间部分丢失或错位 输入datetime带tzinfo 统一转为UTC后再剥离tzinfo写入 部分,需人工定义目标时区 单元格显示数字但格式为日期 数值类型正确但number_format缺失 检查并补全日期格式掩码 是 历史日期(<1900)显示异常 日期早于1900-01-01 改用文本存储或专用历史数据库 否,XLSX根本不适用于此场景 特别注意:代码演示仅验证了转换逻辑的正确性,不等于端到端效果。真实文件中可能混杂多种日期格式、条件格式或公式生成的日期,这些都需要额外的解析层。遇到 1900 年 3 月前的日期、跨时区协作数据或非标准日历(如农历、财政年)时,必须转入人工审查流程,自动化脚本不应擅自修正。 参考资料与下一步行动 openpyxl 官方日期时间文档:https://openpyxl.readthedocs.io/en/stable/datetime.html Microsoft 关于 1900/1904 日期系统的说明:https://docs.microsoft.com/en-us/office/troubleshoot/excel/1900-and-1904-date-system Microsoft 关于 1900 年闰年 bug 的解释:https://docs.microsoft.com/en-us/office/troubleshoot/excel/wrongly-assumes-1900-is-leap-year 下一步:选取一份你手头有权使用的脱敏 Excel 样本,运行上述 epoch 检测代码,确认其日期系统是否与你的处理逻辑一致。若发现不一致,调整转换参数后再生成翻译交付物。 将这份检查清单用于文档翻译 先决定哪些是文字、哪些是公式和业务编码,再做表格翻译。译后检查不仅包括中文是否变成目标语言,也包括公式、标识符和引用关系有没有被改变。 OmniDoc(产品入口) 提供多格式文档翻译入口,官网介绍了 OCR、术语表与双语对照功能。对希望直接从文件开始处理的团队,这是一条值得用实际样本评估的路径。请使用有权上传的脱敏文件,并用本篇的检查项复核结果;这里不声称 OmniDoc 使用了本文的开源库或内部算法,也不以示例代替产品性能测试。 - 1 -
更新于 2026-10-09