Excel 中 1900 闰年问题
在业余开发在线电子表格应用过程,学习了下 Excel 中对于日期的表示方式,发现了一个有意思的问题,那就是 Excel 会将 1900 年当作是闰年。 本篇博客就这个问题随意延伸聊聊。
Excel 日期表示
Excel 的日期内部存储为自然数,代表从 1900 年 1 月 0 日 开始的计算的某一天。
即:
- 0 代表 1900-01-00
- 1 代表 1900-01-01
- 2 代表 1900-01-02
- …
Excel 日期 BUG
但是由于历史原因(兼容 Lotus),有个永远不会被修复的 Bug 很出名,那就是 1900 年被日期函数认为是 “闰年”。
这导致了,60 这个数字,代表的就是 1900-01-29 这个日期。
Mac 版 的 Excel 默认使用 1904 日期系统,所以没有问题。
Excel 日期 JavaScript 日期互转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 每天的毫秒数
const MILLISECONDS_PER_DAY = 86400000
// 自 1899-31-12 00:00:00 起算的毫秒偏移(相对于 1970-01-01 00:00:00)
// 代表 Excel 中的 0
const OFFSET_SINCE_18991231 = Date.UTC(1899, 11, 31, 0, 0, 0, 0)
// Excel 日期数转 JavaScript 日期对象
function toJsDate(serial) {
// 对于 60,即 1900-02-29,直接当作 1900-03-01
// 而大等于 61 的日期(Excel 中的 1900-03-01 起),则减去 1 修复 BUG
// 不过这也意味着,60,61 输入这两个数值,都输出 1900-03-01 这一天
const elapsedDays = serial < 61 ? serial : serial - 1
const milliseconds = OFFSET_SINCE_18991231 + elapsedDays * MILLISECONDS_PER_DAY
return new Date(milliseconds)
}
// JavaScript 日期对象转 Excel 日期数
function fromJsDate(date) {
let elapsedDays = (date.getTime() - OFFSET_SINCE_18991231) / MILLISECONDS_PER_DAY
elapsedDays = Math.floor(elapsedDays)
return elapsedDays >= 60 ? elapsedDays + 1 : elapsedDays
}
闰年的计算规则
- 能被 3200 整除的年份,也必须能被 172800 整除才是闰年。
- 能被 100 整除的年份,也必须能被 400 整除,才是闰年。
- 能被 4 整除的年份是闰年。
上面并非完整的规则,只是人类也持续不到新的例外规则出现的时候…
写成代码:
1
2
3
4
5
6
7
8
9
function isLeapYear(year) {
if (year % 3200 === 0) {
return year % 172800 === 0
}
if (year % 100 === 0) {
return year % 400 === 0
}
return year % 4 === 0
}