Javascript Дата разбора возвращает странные результаты в Chrome - PullRequest
9 голосов
/ 09 мая 2019

Я наблюдал странное поведение Date в Chrome (Версия 74.0.3729.131 (Официальная сборка) (64-битная версия)).В консоли Chrome Dev был выполнен следующий javascript:

new Date('1894-01-01T00:00:00+01:00')
// result: Mon Jan 01 1894 00:00:00 GMT+0100 (Central European Standard Time)

new Date('1893-01-01T00:00:00+01:00')
// result: Sat Dec 31 1892 23:53:28 GMT+0053 (Central European Standard Time)

Я уже читал о нестандартном разборе даты через ctor Date в разных браузерах, хотя и предоставлял действительные значения ISO8601.Но это более чем странно o_o

В Firefox (Quantum 66.0.3 (64-bit)) те же самые вызовы приводят к ожидаемым объектам Date:

new Date('1894-01-01T00:00:00+01:00')
// result: > Date 1892-12-31T23:00:00.000Z

new Date('1893-01-01T00:00:00+01:00')
// result: > Date 1893-12-31T23:00:00.000Z
  • Является ли этоошибка в Chrome?
  • Мой ввод действителен ISO8601, я думаю?
  • Самый важный вопрос, как мне это исправить?(надеюсь, без разбора входной строки самостоятельно)

Ответы [ 2 ]

3 голосов
/ 09 мая 2019

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

Если мы анализируем дату в формате ISO 8601, маска строки даты выглядит следующим образом:

<yyyy>-<mm>-<dd>T<hh>:<mm>:<ss>(.<ms>)?(Z|(+|-)<hh>:<mm>)?

1.Получение даты и времени отдельно

Строка T отделяет дату от времени.Таким образом, мы можем просто разделить строку ISO на T

var isoString = `2019-05-09T13:26:10.979Z`
var [dateString, timeString] = isoString.split("T")

2.Извлечение параметров даты из строки даты

Итак, мы имеем dateString == "2019-05-09".Теперь довольно просто получить эти параметры отдельно

var [year, month, date] = dateString.split("-").map(Number)

3.Обработка временной строки

С временной строкой мы должны сделать более сложные действия из-за ее изменчивости.
У нас есть timeString == "13:26:10Z" Также возможно timeString == "13:26:10" и timeString == "13:26:10+01:00

var clearTimeString = timeString.split(/[Z+-]/)[0]
var [hours, minutes, seconds] = clearTimeString.split(":").map(Number)

var offset = 0 // we will store offset in minutes, but in negation of native JS Date getTimezoneOffset
if (timeString.includes("Z")) {
    // then clearTimeString references the UTC time
    offset = new Date().getTimezoneOffset() * -1
} else {
    var clearOffset = timeString.split(/[+-]/)[1]
    if (clearOffset) {
        // then we have offset tail
        var negation = timeString.includes("+") ? 1 : -1 // detecting is offset positive or negative
        var [offsetHours, offsetMinutes] = clearOffset.split(":").map(Number)
        offset = (offsetMinutes + offsetHours * 60) * negation
    } // otherwise we do nothing because there is no offset marker
}

На данный момент у нас есть представление данных в числовом формате:
year, month, date, hours, minutes, seconds и offset в минутах.

4.Использование ... собственного конструктора JS Date

Да, мы не можем избежать этого, потому что это слишком круто.JS Date автоматически сопоставляет дату для всех отрицательных и слишком больших значений.Таким образом, мы можем просто передать все параметры в необработанном формате, и конструктор JS Date автоматически создаст для нас правильную дату!

new Date(year, month - 1, date, hours, minutes + offset, seconds)

Вуаля!Вот полностью рабочий пример.

function convertHistoricalDate(isoString) {
  var [dateString, timeString] = isoString.split("T")
  var [year, month, date] = dateString.split("-").map(Number)
  
  var clearTimeString = timeString.split(/[Z+-]/)[0]
  var [hours, minutes, seconds] = clearTimeString.split(":").map(Number)
  
  var offset = 0 // we will store offset in minutes, but in negation of native JS Date getTimezoneOffset
  if (timeString.includes("Z")) {
    // then clearTimeString references the UTC time
    offset = new Date().getTimezoneOffset() * -1
  } else {
    var clearOffset = timeString.split(/[+-]/)[1]
    if (clearOffset) {
      // then we have offset tail
      var negation = timeString.includes("+") ? 1 : -1 // detecting is offset positive or negative
      var [offsetHours, offsetMinutes] =   clearOffset.split(":").map(Number)
      offset = (offsetMinutes + offsetHours * 60) * negation
    } // otherwise we do nothing because there is no offset marker
  }

  return new Date(year, month - 1, date, hours, minutes + offset, seconds)
}

var testDate1 = convertHistoricalDate("1894-01-01T00:00:00+01:00")
var testDate2 = convertHistoricalDate("1893-01-01T00:00:00+01:00")
var testDate3 = convertHistoricalDate("1894-01-01T00:00:00-01:00")
var testDate4 = convertHistoricalDate("1893-01-01T00:00:00-01:00")

console.log(testDate1.toLocaleDateString(), testDate1.toLocaleTimeString())
console.log(testDate2.toLocaleDateString(), testDate2.toLocaleTimeString())
console.log(testDate3.toLocaleDateString(), testDate3.toLocaleTimeString())
console.log(testDate4.toLocaleDateString(), testDate4.toLocaleTimeString())

Примечание

В этом случае мы получаем Date экземпляр со всеми его собственными значениями (такими как .getHours()), равными , нормализованными , включая смещение часового пояса.testDate1.toISOString все равно выдаст странный результат.Но если вы работаете с этой датой, она, вероятно, будет на 100% соответствовать вашим потребностям.

Надеюсь, что помогло:)

1 голос
/ 09 мая 2019

Это может быть в том случае, если все браузеры следуют своим собственным стандартам кодирования форматов даты (но я не уверен в этой части). В любом случае, простым решением для этого является применение метода toISOString.

const today = new Date();
console.log(today.toISOString());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...