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
收工!!