Преобразование Excel Date Serial Number в Date с помощью Javascript

У меня есть следующий код 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, в строку даты?

Ответ 1

Попробуйте следующее:

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

Пользовательский для вас:)

Ответ 2

Я сделал для вас один-лайнер:

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

Ответ 3

Хотя я наткнулся на эту дискуссию спустя годы после ее начала, у меня может быть более простое решение первоначального вопроса - fwiw, вот как я в итоге сделал преобразование из Excel "дней с 1899-12-30" в JS Date Мне было нужно:

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), а затем корректировки минут по отрицательному смещению локального часового пояса.

Ответ 4

Нет необходимости делать какие-либо математические операции, чтобы свести их к одной строке.

// 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 Sheets в серийном формате. Документация предполагает, что у серийного номера может быть десятичная дробь, чтобы выразить часть дня:

Указывает, что поля даты, времени, даты и времени должны быть выведены в виде двойных чисел в формате "серийный номер", как это популярно в Lotus 1-2-3. Целая числовая часть значения (слева от десятичной дроби) считает дни с 30 декабря 1899 года . Дробная часть (справа от десятичной дроби) считает время как часть дня. Например, 1 января 1900 года в полдень будет 2,5, 2, потому что это 2 дня после 30 декабря 1899 года, и 0,5, потому что полдень - это полдня. 1 февраля 1900 года в 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));
}

Ответ 5

Таким образом, у меня возникла та же проблема, затем возникли некоторые решения, но у меня начались проблемы с локалью, часовыми поясами и т.д., Но в итоге я смог добавить необходимую точность

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

Аргумент функции time выбирает отображение всей даты или только даты и времени.

Ответ 6

Технические характеристики:

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

Excel сохраняет даты в виде последовательных серийных номеров, чтобы их можно было использовать в расчетах. 1 января 1900 года - серийный номер 1, а 1 января 2008 года - серийный номер 39448, поскольку после 1 января 1900 года это 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

Время измеряется в ECMAScript в миллисекундах с 1 января 1970 года по Гринвичу. В значениях времени високосные секунды игнорируются. Предполагается, что есть ровно 86 400 000 миллисекунд в день.

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

new Date(value)

Целочисленное значение, представляющее количество миллисекунд с 1 января 1970 года, 00:00:00 UTC (эпоха Unix), без учета високосных секунд. Имейте в виду, что большинство функций меток времени 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

// 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 unlikely that an Excel serial date
  // is gonna contain even seconds.
  //
  return new Date(Math.round((excelSerialDate - daysBeforeUnixEpoch) * 24 * hour) + 12 * hour);
};