使用 Javascript 将 Excel 日期序列号转换为日期
Posted
技术标签:
【中文标题】使用 Javascript 将 Excel 日期序列号转换为日期【英文标题】:Converting Excel Date Serial Number to Date using Javascript 【发布时间】:2013-04-20 05:13:47 【问题描述】:我有以下 javascript 代码将日期(字符串)转换为 Microsoft Excel 中使用的日期序列号:
function JSDateToExcelDate(inDate)
var returnDateTime = 25569.0 + ((inDate.getTime() - (inDate.getTimezoneOffset() * 60 * 1000)) / (1000 * 60 * 60 * 24));
return returnDateTime.toString().substr(0,5);
那么,我该如何做相反的事情呢? (意思是 Javascript 代码将 Microsoft Excel 中使用的日期序列号转换为日期字符串?
【问题讨论】:
您可以使用SSF.format(fmt, val, opts)
。文档在here
【参考方案1】:
简短的回答
new Date(Date.UTC(0, 0, excelSerialDate - 1));
为什么会这样
我真的很喜欢@leggett 和@SteveR 的答案,虽然它们大部分都有效,但我想更深入地了解Date.UTC()
的工作原理。
注意:时区偏移可能存在问题,尤其是对于较早的日期(1970 年之前)。请参阅Browsers, time zones, Chrome 67 Error (historic timezone changes),所以我想留在 UTC,尽可能不依赖任何时间变化。
Excel 日期是基于 1900 年 1 月 1 日的整数(在 PC 上。在 MAC 上它基于 1904 年 1 月 1 日)。假设我们在 PC 上。
1900-01-01 is 1.0
1901-01-01 is 367.0, +366 days (Excel incorrectly treats 1900 as a leap year)
1902-01-01 is 732.0, +365 days (as expected)
JS 中的日期基于Jan 1st 1970 UTC
。如果我们使用Date.UTC(year, month, ?day, ?hour, ?minutes, ?seconds)
,它将返回自基准时间以来的毫秒数,以UTC 表示。它有一些有趣的功能,我们可以利用这些功能为我们带来好处。
除day
外,Date.UTC()
的所有参数的正常范围都是从0开始的。它确实接受这些范围之外的数字,并将输入转换为溢出或下溢其他参数。
Date.UTC(1970, 0, 1, 0, 0, 0, 0) is 0ms
Date.UTC(1970, 0, 1, 0, 0, 0, 1) is 1ms
Date.UTC(1970, 0, 1, 0, 0, 1, 0) is 1000ms
它也可以做早于 1970-01-01 的日期。在这里,我们将天从 0 递减到 1,并增加小时、分钟、秒和毫秒。
Date.UTC(1970, 0, 0, 23, 59, 59, 999) is -1ms
它甚至足够聪明,可以将 0-99 范围内的年份转换为 1900-1999
Date.UTC(70, 0, 0, 23, 59, 59, 999) is -1ms
现在,我们如何表示 1900-01-01?为了更容易根据我喜欢的日期查看输出
new Date(Date.UTC(1970, 0, 1, 0, 0, 0, 0)).toISOString() gives "1970-01-01T00:00:00.000Z"
new Date(Date.UTC(0, 0, 1, 0, 0, 0, 0)).toISOString() gives "1900-01-01T00:00:00.000Z"
现在我们必须处理时区。 Excel 在其日期表示中没有时区的概念,但 JS 有。恕我直言,最简单的解决方法是将所有 Excel 日期输入为 UTC(如果可以的话)。
从 Excel 日期 732.0 开始
new Date(Date.UTC(0, 0, 732, 0, 0, 0, 0)).toISOString() gives "1902-01-02T00:00:00.000Z"
由于上面提到的闰年问题,我们知道它会提前 1 天。我们必须将 day 参数减 1。
new Date(Date.UTC(0, 0, 732 - 1, 0, 0, 0, 0)) gives "1902-01-01T00:00:00.000Z"
需要注意的是,如果我们使用 new Date(year, month, day) 构造函数构造日期,则参数使用您当地的时区。我在 PT (UTC-7/UTC-8) 时区,我得到了
new Date(1902, 0, 1).toISOString() gives me "1902-01-01T08:00:00.000Z"
对于我的单元测试,我使用
new Date(Date.UTC(1902, 0, 1)).toISOString() gives "1902-01-01T00:00:00.000Z"
一个将excel序列日期转换为js日期的Typescript函数是
public static SerialDateToJSDate(excelSerialDate: number): Date
return new Date(Date.UTC(0, 0, excelSerialDate - 1));
并提取要使用的 UTC 日期
public static SerialDateToISODateString(excelSerialDate: number): string
return this.SerialDateToJSDate(excelSerialDate).toISOString().split('T')[0];
【讨论】:
谢谢威廉。出色的彻底调查! 您的回答值得更多关注。我希望您不介意我对其进行编辑以在顶部包含简短答案,但也要保留更深入的解释。 我一点也不介意。我总是乐于接受建设性意见。【参考方案2】:感谢@silkfire 的解决方案! 经过我的验证。我发现当你在东半球时,@silkfire 有正确的答案;西半球则相反。 因此,要处理时区,请参见下文:
function ExcelDateToJSDate(serial)
// Deal with time zone
var step = new Date().getTimezoneOffset() <= 0 ? 25567 + 2 : 25567 + 1;
var utc_days = Math.floor(serial - step);
var utc_value = utc_days * 86400;
var date_info = new Date(utc_value * 1000);
var fractional_day = serial - Math.floor(serial) + 0.0000001;
var total_seconds = Math.floor(86400 * fractional_day);
var seconds = total_seconds % 60;
total_seconds -= seconds;
var hours = Math.floor(total_seconds / (60 * 60));
var minutes = Math.floor(total_seconds / 60) % 60;
return new Date(date_info.getFullYear(), date_info.getMonth(), date_info.getDate(), hours, minutes, seconds);
【讨论】:
【参考方案3】:我真的很喜欢 Gil 的简单回答,但它缺少时区偏移。所以,这里是:
function date2ms(d)
let date = new Date(Math.round((d - 25569) * 864e5));
date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
return date;
【讨论】:
【参考方案4】:这是一个旧线程,但希望我可以节省你准备编写这个 npm 包的时间:
$ npm install
js-excel-date-convert
包使用:
const toExcelDate = require('js-excel-date-convert').toExcelDate;
const fromExcelDate = require('js-excel-date-convert').fromExcelDate;
const jul = new Date('jul 5 1998');
toExcelDate(jul); // 35981 (1900 date system)
fromExcelDate(35981); // "Sun, 05 Jul 1998 00:00:00 GMT"
您可以使用https://docs.microsoft.com/en-us/office/troubleshoot/excel/1900-and-1904-date-system 的示例验证这些结果
代码:
function fromExcelDate (excelDate, date1904)
const daysIn4Years = 1461;
const daysIn70years = Math.round(25567.5 + 1); // +1 because of the leap-year bug
const daysFrom1900 = excelDate + (date1904 ? daysIn4Years + 1 : 0);
const daysFrom1970 = daysFrom1900 - daysIn70years;
const secondsFrom1970 = daysFrom1970 * (3600 * 24);
const utc = new Date(secondsFrom1970 * 1000);
return !isNaN(utc) ? utc : null;
function toExcelDate (date, date1904)
if (isNaN(date)) return null;
const daysIn4Years = 1461;
const daysIn70years = Math.round(25567.5 + 1); // +1 because of the leap-year bug
const daysFrom1970 = date.getTime() / 1000 / 3600 / 24;
const daysFrom1900 = daysFrom1970 + daysIn70years;
const daysFrom1904Jan2nd = daysFrom1900 - daysIn4Years - 1;
return Math.round(date1904 ? daysFrom1904Jan2nd : daysFrom1900);
如果您想知道这是如何工作的,请查看:https://bettersolutions.com/excel/dates-times/1904-date-system.htm
【讨论】:
【参考方案5】:规格:
1) https://support.office.com/en-gb/article/date-function-e36c0c8c-4104-49da-ab83-82328b832349
Excel 将日期存储为连续的序列号,以便它们可以 用于计算。 1900 年 1 月 1 日是序列号 1,而 1 月 1, 2008 是序列号 39448,因为它是一月之后的 39,447 天 1900 年 1 月。
2) 还有:https://support.microsoft.com/en-us/help/214326/excel-incorrectly-assumes-that-the-year-1900-is-a-leap-year
当 Microsoft Multiplan 和 Microsoft Excel 发布时,它们还 假设 1900 年是闰年。这一假设允许微软 Multiplan 和 Microsoft Excel 使用相同的序列日期系统 由 Lotus 1-2-3 提供,并提供与 Lotus 1-2-3 的更大兼容性。 将 1900 年视为闰年也让用户更容易移动 从一个程序到另一个程序的工作表。
3)https://www.ecma-international.org/ecma-262/9.0/index.html#sec-time-values-and-time-range
自 1970 年 1 月 1 日以来,时间在 ECMAScript 中以毫秒为单位测量 世界标准时间。在时间值中,闰秒被忽略。假设有 正好是每天 86,400,000 毫秒。
4) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Unix_timestamp
new Date(value)
一个整数值,表示自以来的毫秒数 1970 年 1 月 1 日 00:00:00 UTC(Unix 纪元),闰秒 忽略。请记住,大多数 Unix Timestamp 函数仅 精确到秒。
把它放在一起:
function xlSerialToJsDate(xlSerial)
// milliseconds since 1899-31-12T00:00:00Z, corresponds to xl serial 0.
var xlSerialOffset = -2209075200000;
var elapsedDays;
// each serial up to 60 corresponds to a valid calendar date.
// serial 60 is 1900-02-29. This date does not exist on the calendar.
// we choose to interpret serial 60 (as well as 61) both as 1900-03-01
// so, if the serial is 61 or over, we have to subtract 1.
if (xlSerial < 61)
elapsedDays = xlSerial;
else
elapsedDays = xlSerial - 1;
// javascript dates ignore leap seconds
// each day corresponds to a fixed number of milliseconds:
// 24 hrs * 60 mins * 60 s * 1000 ms
var millisPerDay = 86400000;
var jsTimestamp = xlSerialOffset + elapsedDays * millisPerDay;
return new Date(jsTimestamp);
作为单行:
function xlSerialToJsDate(xlSerial)
return new Date(-2209075200000 + (xlSerial - (xlSerial < 61 ? 0 : 1)) * 86400000);
【讨论】:
天哪。情节变厚了。 support.office.com/en-us/article/…【参考方案6】:所以,我遇到了同样的问题,然后出现了一些解决方案,但开始在语言环境、时区等方面遇到问题,但最终能够增加所需的精度
toDate(serialDate, time = false)
let locale = navigator.language;
let offset = new Date(0).getTimezoneOffset();
let date = new Date(0, 0, serialDate, 0, -offset, 0);
if (time)
return serialDate.toLocaleTimeString(locale)
return serialDate.toLocaleDateString(locale)
函数的“时间”参数选择显示整个日期还是只显示日期的时间
【讨论】:
【参考方案7】:无需进行任何数学运算即可将其简化为一行。
// serialDate is whole number of days since Dec 30, 1899
// offsetUTC is -(24 - your timezone offset)
function SerialDateToJSDate(serialDate, offsetUTC)
return new Date(Date.UTC(0, 0, serialDate, offsetUTC));
我在太平洋标准时间,即 UTC-0700,所以我使用 offsetUTC = -17
将 00:00 作为时间 (24 - 7 = 17)。
如果您以串行格式从 Google 表格中读取日期,这也很有用。 The documentation 建议序列号可以用小数表示一天的一部分:
指示日期、时间、日期时间和持续时间字段以“序列号”格式作为双精度输出,正如 Lotus 1-2-3 所推广的那样。值的整数部分(小数点左侧)计算自 1899 年 12 月 30 日以来的天数。小数部分(小数点右侧)将时间计算为一天的小数部分。例如, 1900 年 1 月 1 日中午将是 2.5,2 因为它是 1899 年 12 月 30 日之后的 2 天,而 0.5 因为中午是半天。 1900 年 2 月 1 日下午 3 点将是 33.625。这正确地将 1900 年视为非闰年。
因此,如果您想支持带小数的序列号,则需要将其分开。
function SerialDateToJSDate(serialDate)
var days = Math.floor(serialDate);
var hours = Math.floor((serialDate % 1) * 24);
var minutes = Math.floor((((serialDate % 1) * 24) - hours) * 60)
return new Date(Date.UTC(0, 0, serialDate, hours-17, minutes));
【讨论】:
这对我有用.. 你需要抵消 UTC 否则你会得到错误的日期。 此解决方案适用于 UTC-5 到 UTC+5:30。我只测试了那些范围时区 这个答案非常接近,但并不完全正确。 William's answer here 实际上更准确。如果您坚持使用 UTC 功能,则实际上不需要调整时区。【参考方案8】:虽然我在讨论开始多年后偶然发现了这个讨论,但我可能对最初的问题有一个更简单的解决方案——fwiw,这是我最终将 Excel“自 1899 年 12 月 30 日以来的天数”转换为的方式我需要的 JS 日期:
var exdate = 33970; // represents Jan 1, 1993
var e0date = new Date(0); // epoch "zero" date
var offset = e0date.getTimezoneOffset(); // tz offset in min
// calculate Excel xxx days later, with local tz offset
var jsdate = new Date(0, 0, exdate-1, 0, -offset, 0);
jsdate.toJSON() => '1993-01-01T00:00:00.000Z'
本质上,它只是构建一个新的 Date 对象,该对象通过添加 Excel 天数(从 1 开始),然后通过负本地时区偏移量调整分钟来计算。
【讨论】:
我喜欢这个解决方案,因为它很优雅。【参考方案9】:// Parses an Excel Date ("serial") into a
// corresponding javascript Date in UTC+0 timezone.
//
// Doesn't account for leap seconds.
// Therefore is not 100% correct.
// But will do, I guess, since we're
// not doing rocket science here.
//
// https://www.pcworld.com/article/3063622/software/mastering-excel-date-time-serial-numbers-networkdays-datevalue-and-more.html
// "If you need to calculate dates in your spreadsheets,
// Excel uses its own unique system, which it calls Serial Numbers".
//
lib.parseExcelDate = function (excelSerialDate)
// "Excel serial date" is just
// the count of days since `01/01/1900`
// (seems that it may be even fractional).
//
// The count of days elapsed
// since `01/01/1900` (Excel epoch)
// till `01/01/1970` (Unix epoch).
// Accounts for leap years
// (19 of them, yielding 19 extra days).
const daysBeforeUnixEpoch = 70 * 365 + 19;
// An hour, approximately, because a minute
// may be longer than 60 seconds, see "leap seconds".
const hour = 60 * 60 * 1000;
// "In the 1900 system, the serial number 1 represents January 1, 1900, 12:00:00 a.m.
// while the number 0 represents the fictitious date January 0, 1900".
// These extra 12 hours are a hack to make things
// a little bit less weird when rendering parsed dates.
// E.g. if a date `Jan 1st, 2017` gets parsed as
// `Jan 1st, 2017, 00:00 UTC` then when displayed in the US
// it would show up as `Dec 31st, 2016, 19:00 UTC-05` (Austin, Texas).
// That would be weird for a website user.
// Therefore this extra 12-hour padding is added
// to compensate for the most weird cases like this
// (doesn't solve all of them, but most of them).
// And if you ask what about -12/+12 border then
// the answer is people there are already accustomed
// to the weird time behaviour when their neighbours
// may have completely different date than they do.
//
// `Math.round()` rounds all time fractions
// smaller than a millisecond (e.g. nanoseconds)
// but it's unlikely that an Excel serial date
// is gonna contain even seconds.
//
return new Date(Math.round((excelSerialDate - daysBeforeUnixEpoch) * 24 * hour) + 12 * hour);
;
【讨论】:
【参考方案10】:我为你做了一个单行:
function ExcelDateToJSDate(date)
return new Date(Math.round((date - 25569)*86400*1000));
【讨论】:
@pappadog 我发现日期可能会相差 1 毫秒,并且比 Silkfire 答案中提供的 0.0000001 偏移更准确。 Math.round 对我不起作用。不过,它可以删除它。 这个单行函数时间不准确。但日期是正确的。 似乎没有考虑时区。例如,我在使用 UTC+8 时间的中国。使用 43556.1265740741 我得到4/1/2019, 11:02:16 AM
比 excel 值 2019/4/1 3:02:16
晚 8 小时。
我建立了一个测试来验证 14 年的日期数字是否转换为与 Excel 相同的日期(忽略时区)。所以这个单线摇滚!【参考方案11】:
试试这个:
function ExcelDateToJSDate(serial)
var utc_days = Math.floor(serial - 25569);
var utc_value = utc_days * 86400;
var date_info = new Date(utc_value * 1000);
var fractional_day = serial - Math.floor(serial) + 0.0000001;
var total_seconds = Math.floor(86400 * fractional_day);
var seconds = total_seconds % 60;
total_seconds -= seconds;
var hours = Math.floor(total_seconds / (60 * 60));
var minutes = Math.floor(total_seconds / 60) % 60;
return new Date(date_info.getFullYear(), date_info.getMonth(), date_info.getDate(), hours, minutes, seconds);
为您定制:)
【讨论】:
即使 1970 - 1900 = 25567 天,为什么还要减去 25569?并不是说这是我发现的第一个在线代码,因此它实际上可以正常工作。 测试了几种解决方案,这是第一个/唯一一个给我预期值的解决方案 @silkfire,呵呵,下一个好奇;)情况很简单,我的同事 - 远程服务人员,可以访问损坏的日志文件,其中 excel 日期未转换为可读格式,我只是写了快速JS,所以他可以在线查看值,而无需在目标机器上安装Excel或任何其他程序,只需打开站点并写入值即可。最后。 @biesior 听起来不错!干得好,很高兴我写的 sn-p 对你有帮助。 你能通过改变时区来测试你的解决方案吗?我在 PST 时区我不得不负 25568,然后它工作但在 UTC+10:00 它不起作用,请检查 UTC+ 和utc-时区。以上是关于使用 Javascript 将 Excel 日期序列号转换为日期的主要内容,如果未能解决你的问题,请参考以下文章
无法使用 Excel JavaScript API 设置 NumberFormat