42

我有以下 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);

}

那么,我该如何做相反的事情呢?(意味着将 Microsoft Excel 中使用的日期序列号转换为日期字符串的 Javascript 代码?

4

11 回答 11

70

尝试这个:

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

为你量身定做:)

于 2013-04-26T09:55:28.580 回答
45

我为你做了一个单行:

function ExcelDateToJSDate(date) {
  return new Date(Math.round((date - 25569)*86400*1000));
}
于 2014-03-12T13:23:31.513 回答
11

无需进行任何数学运算即可将其归结为一行。

// 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 表格中读取日期,这也很有用。该文档建议该序列可以有一个小数来表示一天的一部分:

指示日期、时间、日期时间和持续时间字段以“序列号”格式作为双精度输出,正如 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));
}
于 2019-03-24T06:07:05.720 回答
11

眼镜:

1) https://support.office.com/en-gb/article/date-function-e36c0c8c-4104-49da-ab83-82328b832349

Excel 将日期存储为连续的序列号,以便可以在计算中使用它们。1900 年 1 月 1 日是序列号 1,而 2008 年 1 月 1 日是序列号 39448,因为它是 1900 年 1 月 1 日之后的 39,447 天。

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 年是闰年。这种假设允许 Microsoft 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 日 UTC 以来,时间在 ECMAScript 中以毫秒为单位进行测量。在时间值中,闰秒被忽略。假设每天正好有 86,400,000 毫秒。

4) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Un​​ix_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);
}
于 2019-07-24T13:47:27.453 回答
5

简短的回答

new Date(Date.UTC(0, 0, excelSerialDate - 1));

为什么这样有效

我真的很喜欢@leggett 和@SteveR 的答案,虽然它们大部分都有效,但我想更深入地了解它们是如何Date.UTC()工作的。

注意:时区偏移可能存在问题,尤其是对于较旧的日期(1970 年之前)。请参阅浏览器、时区、Chrome 67 错误(历史时区更改),因此我想留在 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 为单位。它有一些有趣的功能,我们可以利用这些功能为我们带来好处。

的参数的所有正常范围Date.UTC()都是从 0 开始的,除了day. 它确实接受这些范围之外的数字,并将输入转换为溢出或下溢其他参数。

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];
 }
于 2021-04-16T18:11:38.340 回答
4

虽然我在讨论开始多年后偶然发现了这个讨论,但我可能对原始问题有一个更简单的解决方案——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 开始),然后通过负本地时区偏移量调整分钟数来计算的。

于 2018-10-01T22:38:42.167 回答
2

我真的很喜欢 Gil 的简单回答,但它缺少时区偏移。所以,这里是:

function date2ms(d) {
  let date = new Date(Math.round((d - 25569) * 864e5));
  date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
  return date;
}
于 2020-09-28T18:31:46.097 回答
1

所以,我遇到了同样的问题,然后出现了一些解决方案,但开始在语言环境、时区等方面遇到问题,但最终能够增加所需的精度

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

该函数的“时间”参数在显示整个日期或仅显示日期时间之间进行选择

于 2019-06-04T18:34:18.360 回答
0
// 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);
};
于 2017-09-07T13:31:26.640 回答
-1

感谢@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);
}
于 2020-12-28T02:20:42.803 回答
-2

这是一个旧线程,但希望我可以节省您准备编写此 npm 包的时间:

$ npm install js-excel-日期转换

包装用途:

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

于 2019-12-02T04:04:36.713 回答