Converting Excel Date Serial Number to Date using Javascript

后端 未结 9 1293
终归单人心
终归单人心 2020-11-29 02:22

I have the following javascript code that convert date (string) to the Date Serial Number used in Microsoft Excel:

function JSDateToExcelDate(inDate) {

             


        
相关标签:
9条回答
  • 2020-11-29 02:50

    No need to do any math to get it down to one line.

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

    I'm in PST which is UTC-0700 so I used offsetUTC = -17 to get 00:00 as the time (24 - 7 = 17).

    This is also useful if you are reading dates out of Google Sheets in serial format. The documentation suggests that the serial can have a decimal to express part of a day:

    Instructs date, time, datetime, and duration fields to be output as doubles in "serial number" format, as popularized by Lotus 1-2-3. The whole number portion of the value (left of the decimal) counts the days since December 30th 1899. The fractional portion (right of the decimal) counts the time as a fraction of the day. For example, January 1st 1900 at noon would be 2.5, 2 because it's 2 days after December 30st 1899, and .5 because noon is half a day. February 1st 1900 at 3pm would be 33.625. This correctly treats the year 1900 as not a leap year.

    So, if you want to support a serial number with a decimal, you'd need to separate it out.

    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));
    }
    
    0 讨论(0)
  • 2020-11-29 02:53

    Try this:

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

    Custom made for you :)

    0 讨论(0)
  • 2020-11-29 02:53

    Specs:

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

    Excel stores dates as sequential serial numbers so that they can be used in calculations. January 1, 1900 is serial number 1, and January 1, 2008 is serial number 39448 because it is 39,447 days after January 1, 1900.

    2) But also: https://support.microsoft.com/en-us/help/214326/excel-incorrectly-assumes-that-the-year-1900-is-a-leap-year

    When Microsoft Multiplan and Microsoft Excel were released, they also assumed that 1900 was a leap year. This assumption allowed Microsoft Multiplan and Microsoft Excel to use the same serial date system used by Lotus 1-2-3 and provide greater compatibility with Lotus 1-2-3. Treating 1900 as a leap year also made it easier for users to move worksheets from one program to the other.

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

    Time is measured in ECMAScript in milliseconds since 01 January, 1970 UTC. In time values leap seconds are ignored. It is assumed that there are exactly 86,400,000 milliseconds per day.

    4) https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#Unix_timestamp

    new Date(value)

    An integer value representing the number of milliseconds since January 1, 1970, 00:00:00 UTC (the Unix epoch), with leap seconds ignored. Keep in mind that most Unix Timestamp functions are only accurate to the nearest second.

    Putting it together:

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

    As one-liner:

    function xlSerialToJsDate(xlSerial){
      return new Date(-2209075200000 + (xlSerial - (xlSerial < 61 ? 0 : 1)) * 86400000);
    }
    
    0 讨论(0)
  • 2020-11-29 02:57

    It's an old thread but hopefully I can save you the time I used readying around to write this npm package:

    $ npm install js-excel-date-convert

    Package Usage:

    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"
    

    You can verify these results with the example at https://docs.microsoft.com/en-us/office/troubleshoot/excel/1900-and-1904-date-system

    The Code:

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

    If you want to know how this works check: https://bettersolutions.com/excel/dates-times/1904-date-system.htm

    0 讨论(0)
  • 2020-11-29 02:58

    Although I stumbled onto this discussion years after it began, I may have a simpler solution to the original question -- fwiw, here is the way I ended up doing the conversion from Excel "days since 1899-12-30" to the JS Date I needed:

    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'
    

    Essentially, it just builds a new Date object that is calculated by adding the # of Excel days (1-based), and then adjusting the minutes by the negative local timezone offset.

    0 讨论(0)
  • 2020-11-29 02:59

    I really liked Gil's answer for it's simplicity, but it lacked the timezone offset. So, here it is:

    function date2ms(d) {
      let date = new Date(Math.round((d - 25569) * 864e5));
      date.setMinutes(date.getMinutes() + date.getTimezoneOffset());
      return date;
    }
    
    0 讨论(0)
提交回复
热议问题