xlsx.js 中的日期 Bug

122 阅读2分钟

Excel 存储格式

参考 zhuanlan.zhihu.com/p/69101012

Excel 将日期值存储为序列号,1900-01-01 序列号是 1。 而 2023-10-01 序列号是 45200。

再来看一段代码

import dayjs from 'dayjs';

const d = dayjs('1900-01-01');
dayjs('1900-01-01').diff(d, 'day') + 1; // 1
dayjs('2023-10-01').diff(d, 'day') + 1; // 45199

这不对啊,不是 45200 么,代码的结果是 45199。

实际这是一个 bug,Excel 会把 1900 当做闰年。见 learn.microsoft.com/en-us/offic…

实际上 1900 不是闰年。

实际上

数值转Date

在这篇文章(www.yuque.com/liyatang/he…)中日期内容为 2023-10-01 ,最终读取出来是 2023-09-30T15:59:17.000Z。

注意,这个是 0 时区表达,+8 区表达是 Sat Sep 30 2023 23:59:17 GMT+0800 (中国标准时间)。

这有点奇怪,找到了源码 github.com/SheetJS/she…

经过测试确实是 2023-09-30T15:59:17.000Z。

var basedate = /*#__PURE__*/ new Date(1899, 11, 30, 0, 0, 0); // 2209161600000
function datenum(v /*:Date*/, date1904 /*:?boolean*/) /*:number*/ {
  var epoch = /*#__PURE__*/ v.getTime();
  if (date1904) epoch -= 1462 * 24 * 60 * 60 * 1000;
  var dnthresh =
    /*#__PURE__*/ basedate.getTime() +
    /*#__PURE__*/ (v.getTimezoneOffset() -
      /*#__PURE__*/ basedate.getTimezoneOffset()) *
      60000;
  return (epoch - dnthresh) / (24 * 60 * 60 * 1000);
}
var refdate = /*#__PURE__*/ new Date();
var dnthresh =
  /*#__PURE__*/ basedate.getTime() +
  /*#__PURE__*/ (refdate.getTimezoneOffset() -
    /*#__PURE__*/ basedate.getTimezoneOffset()) *
    60000;
var refoffset = /*#__PURE__*/ refdate.getTimezoneOffset();
function numdate(v /*:number*/) /*:Date*/ {
  var out = new Date();
  out.setTime(v * 24 * 60 * 60 * 1000 + dnthresh);
  if (out.getTimezoneOffset() !== refoffset) {
    out.setTime(out.getTime() + (out.getTimezoneOffset() - refoffset) * 60000);
  }
  return out;
}

numdate(45200); // 2023-09-30T15:59:17.000Z

那怎么解决?

看到别人提供的方案是这样,通过 fixPrecisionLoss 再修正下

const importBugHotfixDiff = (function () {
  function getTimezoneOffsetMS(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;
  }

  const baseDate = new Date(1899, 11, 30, 0, 0, 0);
  const nD = new Date();
  const dnthreshAsIs =
    (nD.getTimezoneOffset() - baseDate.getTimezoneOffset()) * 60000;
  const dnthreshToBe = getTimezoneOffsetMS(nD) - getTimezoneOffsetMS(baseDate);
  return dnthreshAsIs - dnthreshToBe;
})();

function fixPrecisionLoss(date) {
  return new Date(date.getTime() - importBugHotfixDiff);
}

再换算下

var basedate = /*#__PURE__*/ new Date(1899, 11, 30, 0, 0, 0); // 2209161600000
function datenum(v /*:Date*/, date1904 /*:?boolean*/) /*:number*/ {
  var epoch = /*#__PURE__*/ v.getTime();
  if (date1904) epoch -= 1462 * 24 * 60 * 60 * 1000;
  var dnthresh =
    /*#__PURE__*/ basedate.getTime() +
    /*#__PURE__*/ (v.getTimezoneOffset() -
      /*#__PURE__*/ basedate.getTimezoneOffset()) *
      60000;
  return (epoch - dnthresh) / (24 * 60 * 60 * 1000);
}
var refdate = /*#__PURE__*/ new Date();
var dnthresh =
  /*#__PURE__*/ basedate.getTime() +
  /*#__PURE__*/ (refdate.getTimezoneOffset() -
    /*#__PURE__*/ basedate.getTimezoneOffset()) *
    60000;
var refoffset = /*#__PURE__*/ refdate.getTimezoneOffset();
function numdate(v /*:number*/) /*:Date*/ {
  var out = new Date();
  out.setTime(v * 24 * 60 * 60 * 1000 + dnthresh);
  if (out.getTimezoneOffset() !== refoffset) {
    out.setTime(out.getTime() + (out.getTimezoneOffset() - refoffset) * 60000);
  }
  return out;
}

numdate(45200);

const importBugHotfixDiff = (function () {
  function getTimezoneOffsetMS(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;
  }

  const baseDate = new Date(1899, 11, 30, 0, 0, 0);
  const nD = new Date();
  const dnthreshAsIs =
    (nD.getTimezoneOffset() - baseDate.getTimezoneOffset()) * 60000;
  const dnthreshToBe = getTimezoneOffsetMS(nD) - getTimezoneOffsetMS(baseDate);
  return dnthreshAsIs - dnthreshToBe;
})();

function fixPrecisionLoss(date) {
  return new Date(date.getTime() - importBugHotfixDiff);
}

fixPrecisionLoss(numdate(45200)); // 2023-09-30T16:00:00.000Z

收工!!