问题背景
在使用 SheetJS.xlsx
库调用 xlsx.read(data, { cellDates: true })
解析 Excel 文件时会把单元格为日期类型的数据直接转化为 Date
对象。但是在实际使用时,解析的 Date
总是有 43
秒的误差。例如在 Excel 中日期类型的单元格值为 2023/8/15
,但是解析的结果则为 Mon Aug 14 2023 23:59:17 GMT+0800 (中国标准时间)
的 Date 对象;同样其他日期也是与实际解析结果有 43
秒误差。
根本原因
- 在 Excel 中,日期是被存储为自 1899 年 12 月 30 日以来的天数,以整数形式存储。
- 中国时区在 1899 年的时差偏移量是
+8:05:43
(附:时区变化) - SheetJS 使用了
getTimezoneOffset
来解决误差,但是getTimezoneOffset
以整数形式返回分钟部分,忽略了秒的部分,所以造成了 43 秒的误差。
解决方法
在 xlsx.utils.sheet_to_json
后对 Date
类型的数据调用 fixPrecisionLoss(date)
进行精度修正。
// 返回当前时区与该日期所在时区之间的时间差(以毫秒为单位)
function getTimezoneOffsetMS(date: Date) {
const time = date.getTime();
const utcTime = Date.UTC(
date.getFullYear(),
date.getMonth(),
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds(),
date.getMilliseconds(),
);
return time - utcTime;
}
// 计算当前时区与1899年12月30日时区之间的时间差(以毫秒为单位)
const importBugHotfixDiff = (function () {
const baseDate = new Date(1899, 11, 30, 0, 0, 0);
const dnThreshAsIs = (new Date().getTimezoneOffset() - baseDate.getTimezoneOffset()) * 60000;
const dnThreshToBe = getTimezoneOffsetMS(new Date()) - getTimezoneOffsetMS(baseDate);
return dnThreshAsIs - dnThreshToBe;
}());
// 修复日期的精度损失(需要注意传入的 Date 对象必须是已经转换为当前时区 Date 对象)
function fixPrecisionLoss(date: Date) {
return (new Date(date.getTime() - importBugHotfixDiff));
}