如何解析你,Excel 的 Date 呀

4,529 阅读3分钟

简单的背景介绍

不久前,我们接了一个自己做前端后端产品的活,从此过上了可怜巴巴敲代码开开心心收获知识的日子呢。

那是一个平平无奇的周一下午

用户小姐姐在群里说,系统筛选工卡有效期不好使。(系统:不不不,不是我的锅

我看了一下数据库,发现,我们原定的有效期格式是这样的

整整齐齐。

数据库中当时的数据是这样的

甚至是这样的

看到这种情况,我觉得肯定是输入的时候输的不太对(年轻…

于是我决定从 Excel 下手

小姐姐们的操作流程是先用我们的系统导出一份 Excel,编辑之后再导入系统的,那只要我把这工卡一列的格式限制为日期,就一定可以统一格式的,嗯。

我们项目使用了 js-xlsx 处理表格的导入导出,下面是导出 Excel 的伪代码:

import * as XLSX from 'xlsx';

const xlsxMineType = 'application/vnd.openxmlformats-
officedocument.spreadsheetml.sheet'
const data = 数据.map((s: any) => ({
ID: s.id,
工卡有效期: s.card_expired,
……
}));

const sheet = XLSX.utils.json_to_sheet(data);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, sheet, '员工信息表');
const wbbuf = XLSX.write(wb, {
  type: 'base64'
});

this.success({ name: "员工信息表.xlsx", data: wbbuf, type:xlsxMineType });

通过 json_to_sheet 可以拿到包含单元格信息的对象

{ 
 A2: { t: 'n', v: 3776 },
 B2: { t: 's', v: '2019-04-01' },
 A3: { t: 'n', v: 3831 },
 B3: { t: 's', v: '2019-04-01' },
 A1: { t: 's', v: 'ID' },
 B1: { t: 's', v: '工卡有效期' },
 '!ref': 'A1:B3' 
}

对象中以单元格位置作为 key,每个单元格的值(v)、类型(t)等等属性作为 value。其中单元格的类型支持:

b Boolean, n Number, e error, s String, d Date

看起来 Date 类型十分符合上面的要求,就尝试了一下:

const sheet = XLSX.utils.json_to_sheet(data);
// 筛选出除表头的工卡列
Object.keys(sheet).filter(item => /^B/.test(item) && item !== "B1").forEach(key => {
   sheet[key].t = "d";
})

然鹅,如果工卡有效期本来就为空,这时候导出,打开 Excel 会报错,并且空的位置会变成 NaN

翻阅了各种中英文文档、Issue,导出一百多个员工信息表之后,我发现 Excel 真的很奇妙,或许应该在 js 上来格式化导入的数据,而不是限制单元格的类型。

如果不控制单元格类型的话,那么当管理员输入日期的时候,这个单元格可能是:文本、常规、日期、自定义类型,所以只要保证不管单元格是什么格式,程序都能拿到正确的数据就好了。

当管理员使的工卡有效期的单元格类型是文本或者常规的时候,则比较简单,程序可以按预期解析出来一个相应的字符串,用 moment 解析一下,就可以获得想要的格式的数据了。

那么当有效期单元格的类型是日期和自定义的时候,我们拿到的数据是像下图一样

这也就是之前数据库中奇怪的数字的由来,这个数字的意义,其实是当前日期距离 1900 年 1月 0 日的天数。还需要注意的是,Excel 中有个 bug:

它以为 1900 年是闰年,所以我们拿到的天数都会多了一天,因为转换之前还需要先进行减一操作…

item.工卡有效期 = new Date(1900, 0, expried - 1)

这样之后就可以拿到正确的日期啦。咕叽。