Почему Date.parse дает неверные результаты? - PullRequest
317 голосов
/ 06 апреля 2010

Случай один:

new Date(Date.parse("Jul 8, 2005"));

Выход:

Пт, 08 июля 2005 00:00:00 GMT-0700 (PST)

Случай второй:

new Date(Date.parse("2005-07-08"));

Выход:

Чт, 07 июля 2005 17:00:00 GMT-0700 (PST)

<ч />

Почему второй анализ неверен?

Ответы [ 11 ]

425 голосов
/ 06 апреля 2010

До выхода спецификации 5-го издания метод Date.parse полностью зависел от реализации (new Date(string) эквивалентен Date.parse(string), за исключением последний возвращает число, а не Date). В спецификации 5-го издания было добавлено требование для поддержки упрощенного (и немного неправильного) ISO-8601 (также см. Что такое допустимые строки даты и времени в JavaScript? ). Но кроме этого, не требовалось для того, что Date.parse / new Date(string) должно принимать, кроме того, что они должны были принимать любой вывод Date # toString (не говоря, что это было ).

Начиная с ECMAScript 2017 (редакция 8), реализации должны были анализировать свои выходные данные для Date # toString и Date # toUTCString , но формат этих строк не был указан.

Начиная с ECMAScript 2019 (издание 9), формат для Дата # toString и Дата # toUTCString , определены как (соответственно):

  1. ддд МММ ДД ГГГГ ЧЧ: мм: сс ZZ [(название часового пояса)]
    например. Вт 10 июля 2018 18:39:58 GMT + 0530 (IST)
  2. ддд, ДД МММ ГГГГ ЧЧ: мм: сс Z
    например. Вт 10 июл 2018 13:09:58 GMT

предоставляя еще 2 формата, которые Date.parse должен надежно анализировать в новых реализациях (отмечая, что поддержка не является повсеместной, и несовместимые реализации будут использоваться некоторое время).

Я бы порекомендовал, чтобы строки даты анализировались вручную, а конструктор Date использовался с аргументами год, месяц и день, чтобы избежать двусмысленности:

// parse a date in yyyy-mm-dd format
function parseDate(input) {
  var parts = input.split('-');
  // new Date(year, month [, day [, hours[, minutes[, seconds[, ms]]]]])
  return new Date(parts[0], parts[1]-1, parts[2]); // Note: months are 0-based
}
186 голосов
/ 09 декабря 2013

Во время недавнего опыта написания интерпретатора JS я много боролся с внутренней работой дат ECMA / JS. Итак, я полагаю, что я добавлю свои 2 цента здесь. Надеюсь, что обмен этими материалами поможет другим с любыми вопросами о различиях между браузерами в том, как они обрабатывают даты.

Сторона ввода

Все реализации хранят свои значения даты внутри как 64-битные числа, которые представляют количество миллисекунд с 01.01.1970 UTC (GMT - то же самое, что и UTC). Даты после 1/1/1970 00:00:00 являются положительными числами, а даты до являются отрицательными.

Следовательно, следующий код дает одинаковый результат во всех браузерах.

Date.parse('1/1/1970');

В моем часовом поясе (EST) результат равен 18000000, потому что это количество мс за 5 часов (это всего 4 часа в летнее время). Значение будет отличаться в разных часовых поясах. Все основные браузеры делают это одинаково.

Вот загвоздка, хотя. Хотя в форматах входных строк есть некоторые различия, которые основные браузеры будут анализировать как даты, они, по сути, интерпретируют их так же, как часовые пояса и переход на летнее время. Один из них - это формат ISO 8601. Это единственный формат, описанный в спецификации ECMA-262 v.5. Для всех других форматов строк интерпретация зависит от реализации. По иронии судьбы, это формат, в котором браузеры могут отличаться. Вот сравнительный вывод Chrome и Firefox за 01.01.1970 на моей машине с использованием строкового формата ISO 8601.

Date.parse('1970-01-01T00:00:00Z');       // Chrome: 0         FF: 0
Date.parse('1970-01-01T00:00:00-0500');   // Chrome: 18000000  FF: 18000000
Date.parse('1970-01-01T00:00:00');        // Chrome: 0         FF: 18000000
  • Спецификатор «Z» указывает, что вход уже находится во времени UTC и не требует смещения перед сохранением.
  • Спецификатор "-0500" указывает, что ввод в GMT-05: 00, поэтому оба браузеры интерпретируют ввод как находящийся в моем местном часовом поясе. Это означает, что значение преобразуется в UTC перед сохранением. В моем случае это означает добавление 18000000 мс к внутреннему значению даты, поэтому требуется сдвиг -18000000 мс (-05: 00), чтобы вернуть меня по местному времени.
  • Однако при отсутствии спецификатора FF обрабатывает ввод как местное время, тогда как Chrome рассматривает это как время UTC. Для меня это создает 5-часовую разницу в сохраненном значении, что проблематично. В моей реализации я остановился на FF, потому что мне нравится, когда вывод toString совпадает с моим входным значением, если я не укажу альтернативный часовой пояс, чего я никогда не делаю. Отсутствие спецификатора должно предполагать ввод местного времени.

Но здесь ситуация ухудшается: FF обрабатывает краткую форму формата ISO 8601 («ГГГГ-ММ-ДД») иначе, чем длинную форму («ГГГГ-ММ-ДДГЧ: мм: сс: sssZ»). ") без всякой логической причины. Вот выходные данные FF с длинными и короткими форматами даты ISO без указания часового пояса.

Date.parse('1970-01-01T00:00:00');       // 18000000
Date.parse('1970-01-01');                // 0

Итак, чтобы непосредственно ответить на первоначальный вопрос Аскера, "YYYY-MM-DD" - это краткая форма формата ISO 8601 "YYYY-MM-DDTHH:mm:ss:sssZ". Таким образом, это интерпретируется как время UTC, в то время как другое интерпретируется как местное. Вот почему

Это не джайв:

console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08")).toString());

Это делает:

console.log(new Date(Date.parse("Jul 8, 2005")).toString());
console.log(new Date(Date.parse("2005-07-08T00:00:00")).toString());

Суть в том, что для разбора строк даты. Единственная строка ISO 8601, которую вы можете безопасно анализировать в разных браузерах, - это длинная форма. И ВСЕГДА используйте спецификатор "Z". Если вы сделаете это, вы можете спокойно переходить между местным и UTC временем.

Это работает во всех браузерах (после IE9):

console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());

К счастью, большинство современных браузеров обрабатывают другие форматы ввода одинаково, включая наиболее часто используемые форматы «01.01.1970» и «01.01.1970 00:00:00 AM». Все следующие форматы (и другие) обрабатываются как ввод местного времени во всех браузерах и перед сохранением преобразуются в UTC. Таким образом, делая их кросс-браузерными. Вывод этого кода одинаков во всех браузерах в моем часовом поясе.

console.log(Date.parse("1/1/1970"));
console.log(Date.parse("1/1/1970 12:00:00 AM"));
console.log(Date.parse("Thu Jan 01 1970"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00"));
console.log(Date.parse("Thu Jan 01 1970 00:00:00 GMT-0500"));

Выходная сторона

Со стороны вывода все браузеры переводят часовые пояса одинаково, но по-разному обрабатывают строковые форматы. Вот функции toString и что они выводят. Обратите внимание, что функции toUTCString и toISOString выводятся на мою машину в 5:00 утра.

Преобразует из UTC в Местное время перед печатью

 - toString
 - toDateString
 - toTimeString
 - toLocaleString
 - toLocaleDateString
 - toLocaleTimeString

Печатает сохраненное время UTC напрямую

 - toUTCString
 - toISOString 

<b>In Chrome</b>
toString            Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString        Thu Jan 01 1970
toTimeString        00:00:00 GMT-05:00 (Eastern Standard Time)
toLocaleString      1/1/1970 12:00:00 AM
toLocaleDateString  1/1/1970
toLocaleTimeString  00:00:00 AM

toUTCString         Thu, 01 Jan 1970 05:00:00 GMT
toISOString         1970-01-01T05:00:00.000Z

<b>In Firefox</b>
toString            Thu Jan 01 1970 00:00:00 GMT-05:00 (Eastern Standard Time)
toDateString        Thu Jan 01 1970
toTimeString        00:00:00 GMT-0500 (Eastern Standard Time)
toLocaleString      Thursday, January 01, 1970 12:00:00 AM
toLocaleDateString  Thursday, January 01, 1970
toLocaleTimeString  12:00:00 AM

toUTCString         Thu, 01 Jan 1970 05:00:00 GMT
toISOString         1970-01-01T05:00:00.000Z

Обычно я не использую формат ISO для ввода строк. Единственный раз, когда использование этого формата выгодно для меня, это когда даты должны быть отсортированы в виде строк. Формат ISO сортируется как есть, а остальные нет. Если вам нужна совместимость с разными браузерами, укажите часовой пояс или используйте совместимый формат строки.

Код new Date('12/4/2013').toString() проходит через следующее внутреннее псевдопреобразование:

  "12/4/2013" -> toUCT -> [storage] -> toLocal -> print "12/4/2013"

Надеюсь, этот ответ был полезен.

70 голосов
/ 14 марта 2012

Есть какой-то метод к безумию.Как правило, если браузер может интерпретировать дату как ISO-8601, это так.«2005-07-08» попадает в этот лагерь, и поэтому он анализируется как UTC.«8 июля 2005 г.» не может, и поэтому он анализируется по местному времени.

См. JavaScript и даты, что за беспорядок! , чтобы узнать больше.

7 голосов
/ 01 марта 2013

Другое решение - создать ассоциативный массив с форматом даты, а затем переформатировать данные.

Этот метод полезен для даты, отформатированной необычным способом.

Пример:

    mydate='01.02.12 10:20:43':
    myformat='dd/mm/yy HH:MM:ss';


    dtsplit=mydate.split(/[\/ .:]/);
    dfsplit=myformat.split(/[\/ .:]/);

    // creates assoc array for date
    df = new Array();
    for(dc=0;dc<6;dc++) {
            df[dfsplit[dc]]=dtsplit[dc];
            }

    // uses assc array for standard mysql format
    dstring[r] = '20'+df['yy']+'-'+df['mm']+'-'+df['dd'];
    dstring[r] += ' '+df['HH']+':'+df['MM']+':'+df['ss'];
5 голосов
/ 21 июля 2017

Используйте moment.js для разбора дат:

var caseOne = moment("Jul 8, 2005", "MMM D, YYYY", true).toDate();
var caseTwo = moment("2005-07-08", "YYYY-MM-DD", true).toDate();

Третий аргумент определяет строгий синтаксический анализ (доступно с 2.3.0). Без этого moment.js также может давать неверные результаты.

5 голосов
/ 03 сентября 2012

Согласно http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.html формат «гггг / мм / дд» решает обычные проблемы. Он говорит: «Придерживайтесь« ГГГГ / ММ / ДД »для ваших строк дат, когда это возможно. Это универсально поддерживается и однозначно. С этим форматом все времена локальные». Я поставил тесты: http://jsfiddle.net/jlanus/ND2Qg/432/ Этот формат: + позволяет избежать двусмысленности порядка дня и месяца, используя порядок y y d и год из 4 цифр + избегает использования UTC или локальной проблемы, не соответствующей формату ISO, используя косую черту + danvk, парень dygraphs , говорит, что этот формат хорош во всех браузерах.

4 голосов
/ 07 апреля 2010

Хотя CMS верна , что передача строк в метод разбора, как правило, небезопасна, новая спецификация ECMA-262 5-е издание (она же ES5) в разделе 15.9.4.2 предполагает, что Date.parse() фактически должен обрабатывать даты в формате ISO. Старая спецификация не предъявляла таких требований. Конечно, старые браузеры и некоторые современные браузеры по-прежнему не предоставляют эту функциональность ES5.

Ваш второй пример не ошибается. Это указанная дата в формате UTC, как подразумевается Date.prototype.toISOString(), но она указана в вашем местном часовом поясе.

2 голосов
/ 24 января 2018

Оба являются правильными, но они интерпретируются как даты с двумя разными часовыми поясами. Итак, вы сравнили яблоки и апельсины:

// local dates
new Date("Jul 8, 2005").toISOString()            // "2005-07-08T07:00:00.000Z"
new Date("2005-07-08T00:00-07:00").toISOString() // "2005-07-08T07:00:00.000Z"
// UTC dates
new Date("Jul 8, 2005 UTC").toISOString()        // "2005-07-08T00:00:00.000Z"
new Date("2005-07-08").toISOString()             // "2005-07-08T00:00:00.000Z"

Я удалил вызов Date.parse(), поскольку он автоматически используется в строковом аргументе. Я также сравнил даты, используя формат ISO8601 , чтобы вы могли визуально сравнить даты между вашими локальными датами и датами UTC. Время составляет 7 часов, что является разницей в часовых поясах и почему ваши тесты показали две разные даты.

Другой способ создания этих же локальных / UTC-дат будет:

new Date(2005, 7-1, 8)           // "2005-07-08T07:00:00.000Z"
new Date(Date.UTC(2005, 7-1, 8)) // "2005-07-08T00:00:00.000Z"

Но я по-прежнему настоятельно рекомендую Moment.js , который такой же простой, но мощный :

// parse string
moment("2005-07-08").format()       // "2005-07-08T00:00:00+02:00"
moment.utc("2005-07-08").format()   // "2005-07-08T00:00:00Z"
// year, month, day, etc.
moment([2005, 7-1, 8]).format()     // "2005-07-08T00:00:00+02:00"
moment.utc([2005, 7-1, 8]).format() // "2005-07-08T00:00:00Z"
2 голосов
/ 10 марта 2015

Вот короткий, гибкий фрагмент для преобразования строки даты и времени в безопасном для браузера виде, как nicel подробно описано в @ drankin2112.

var inputTimestamp = "2014-04-29 13:00:15"; //example

var partsTimestamp = inputTimestamp.split(/[ \/:-]/g);
if(partsTimestamp.length < 6) {
    partsTimestamp = partsTimestamp.concat(['00', '00', '00'].slice(0, 6 - partsTimestamp.length));
}
//if your string-format is something like '7/02/2014'...
//use: var tstring = partsTimestamp.slice(0, 3).reverse().join('-');
var tstring = partsTimestamp.slice(0, 3).join('-');
tstring += 'T' + partsTimestamp.slice(3).join(':') + 'Z'; //configure as needed
var timestamp = Date.parse(tstring);

Ваш браузер должен предоставить тот же результат отметки времени, что и Date.parse с:

(new Date(tstring)).getTime()
2 голосов
/ 13 августа 2013

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

Пример разбора:

var caseOne = Date.parseDate("Jul 8, 2005", "M d, Y");
var caseTwo = Date.parseDate("2005-07-08", "Y-m-d");

И форматирование обратно в строку (вы заметите, что оба случая дают одинаковый результат):

console.log( caseOne.dateFormat("M d, Y") );
console.log( caseTwo.dateFormat("M d, Y") );
console.log( caseOne.dateFormat("Y-m-d") );
console.log( caseTwo.dateFormat("Y-m-d") );
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...