使用Javascript将Excel日期序列号转换为日期

时间:2013-04-26 05:43:15

标签: javascript excel date

我有以下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代码?

11 个答案:

答案 0 :(得分:31)

试试这个:

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

为您定制:)

答案 1 :(得分:14)

我为你做了一个单行:

function ExcelDateToJSDate(date) {
  return new Date(Math.round((date - 25569)*86400*1000));
}

答案 2 :(得分:1)

无需进行任何数学运算即可将结果降至一行。

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

我在PST(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年视为as年。

因此,如果要支持带小数的序列号,则需要将其分开。

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

答案 3 :(得分:1)

我真的很喜欢Gil的答案,因为它很简单,但是缺少时区偏移。因此,这里是:

function date2ms(d) {
  let date = new Date(Math.round((d - 25569) * 864e5));
  date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
  return date;
}

答案 4 :(得分:0)

尽管我在开始讨论的几年后偶然发现了这个问题,但对于原始问题,我可能有一个更简单的解决方案-顺便说一句,这是我最终从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开始),然后通过负本地时区偏移量来调整分钟来计算。

答案 5 :(得分:0)

所以,我遇到了同样的问题,然后出现了一些解决方案,但是开始遇到了区域设置,时区等问题,但最终却能够增加所需的精度

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

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

答案 6 :(得分:0)

规格:

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

  

Excel将日期存储为序列号,以便可以   用于计算。 1900年1月1日是序列号1,1月   1月1日是2008年1月,因为它是1月之后的39447天   1900年1月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年是a年。这个假设使Microsoft   Multiplan和Microsoft Excel使用相同的序列日期系统   由Lotus 1-2-3提供,并提供与Lotus 1-2-3的更大兼容性。   将1900年视为a年,也使用户更容易移动   从一个程序到另一个程序的工作表。

3)https://www.ecma-international.org/ecma-262/9.0/index.html#sec-time-values-and-time-range

  

时间是自1970年1月1日以来以ECMAScript为单位的毫秒数。   世界标准时间。在时间值上,leap秒将被忽略。假设那里   每天精确地为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时代),带有leap秒   忽略了。请记住,大多数Unix时间戳功能仅   精确到最接近的秒。

将它们放在一起:

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

答案 7 :(得分:0)

这是一个旧线程,但是希望我可以为您节省编写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

答案 8 :(得分:0)

我真的很喜欢 @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 为单位。它有一些有趣的功能,我们可以从中受益。

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

答案 9 :(得分:-1)

Default (*)

答案 10 :(得分:-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);
}