SheetJS.xlsx 解析日期类型丢失 43 秒的问题

726 阅读2分钟

问题背景

在使用 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 秒误差。

根本原因

  1. 在 Excel 中,日期是被存储为自 1899 年 12 月 30 日以来的天数,以整数形式存储。
  2. 中国时区在 1899 年的时差偏移量是 +8:05:43(附:时区变化
  3. 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));
}

参考