记一次神奇的时间转换问题(SheetJS)

最近在写一个功能,使用SheetJS读取Excel表格,在读取日期的时候发现了一个隐藏很深的坑,特此记录一下。
SheetJS读取Excel文件时,可指定参数 cellDates: true,这样当单元格内存储的为日期时,可以将读取到的值直接转化为Date对象。

const workbook = XLSX.read(buffer, { type: 'binary',cellDates: true });

但测试时发现,读取到的值和实际时间总有43秒的误差:
Excel中的值
实际读取到的值
经过搜索,发现这是一个历史原因导致的BUG:

在当地时间1901-01-01 00:00:00,上海时区由LMT(Local Mean Time)切换为CST (China Standard Time),与GMT的时差由+8:05:43调整为+8:00:00。
https://zhuanlan.zhihu.com/p/89914219

在GitHub上也有相关的讨论
GitHub: The exported date is 43 seconds longer

SheetJS用 getTimezoneOffset 来解决这个问题,但 getTimezoneOffset 获取到的时差只精确到分钟,所以就产生了43秒的误差

这个Issue里给出了解决方法

方法1

手动修复误差

function getTimezoneOffsetMS(date) {
  var time = date.getTime();
  var utcTime = Date.UTC(date.getFullYear(),
                         date.getMonth(),
                         date.getDate(),
                         date.getHours(),
                         date.getMinutes(),
                         date.getSeconds(),
                         date.getMilliseconds());
  return time - utcTime;
}

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;
}());

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

SSF 模块对 Date 对象进行转换

import XLSX from 'xlsx';
const basedate = new Date(1899, 11, 30, 0, 0, 0);
const dnthresh = basedate.getTime() + (new Date().getTimezoneOffset() - basedate.getTimezoneOffset()) * 60000;

const day_ms = 24 * 60 * 60 * 1000;
const days_1462_ms = 1462 * day_ms;

function datenum(v: Date, date1904: boolean) {
  let epoch = v.getTime();
  if (date1904) {
    epoch -= days_1462_ms;
  }
  return (epoch - dnthresh) / day_ms;
}

export function fixImportedDate(date: Date, isDate1904: boolean) {
  const parsed = XLSX.SSF.parse_date_code(datenum(date, false), { date1904: isDate1904 });
  // return `${parsed.y}-${parsed.m}-${parsed.d}`;
  return new Date(parsed.y, parsed.m, parsed.d, parsed.H, parsed.M, parsed.S);
}
其他方法

不使用cellDates: true,直接用SSF模块转换日期格式单元格里的值

import XLSX from 'xlsx';

export function parseExcelDate(n: number, isDate1904: boolean) {
  const parsed = XLSX.SSF.parse_date_code(n, { date1904: isDate1904 });
  // return `${parsed.y}-${parsed.m}-${parsed.d}`;
  return new Date(parsed.y, parsed.m, parsed.d, parsed.H, parsed.M, parsed.S);
}
后记

真是醉了,写个代码还能学到历史……

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值