Во время недавнего опыта написания интерпретатора JS я много боролся с внутренней работой дат ECMA / JS. Итак, я полагаю, что я добавлю свои 2 цента здесь. Надеюсь, что обмен этими материалами поможет другим с любыми вопросами о различиях между браузерами в том, как они обрабатывают даты.
Сторона ввода
Все реализации хранят свои значения даты внутри как 64-битные числа, которые представляют количество миллисекунд (мс) с 1970-01-01 UTC (GMT - то же самое, что и UTC). Эта дата является эпохой ECMAScript, которая также используется другими языками, такими как системы Java и POSIX, такими как UNIX. Даты, происходящие после эпохи, являются положительными числами, а даты до являются отрицательными.
Следующий код интерпретируется как одна и та же дата во всех текущих браузерах, но со смещением местного часового пояса:
Date.parse('1/1/1970'); // 1 January, 1970
В моем часовом поясе (EST, то есть -05: 00) результат равен 18000000, потому что это количество мс за 5 часов (это всего 4 часа в летнее время). Значение будет отличаться в разных часовых поясах. Это поведение указано в ECMA-262, поэтому все браузеры делают это одинаково.
Хотя в форматах входных строк есть некоторые различия, которые основные обозреватели будут анализировать как даты, они, по сути, интерпретируют их так же, как часовые пояса и переход на летнее время, даже если синтаксический анализ в значительной степени зависит от реализации.
Однако формат ISO 8601 отличается. Это один из двух форматов, специально описанных в ECMAScript 2015 (ред. 6), который должен быть проанализирован одинаково во всех реализациях (другой - формат, указанный для Date.prototype.toString ).
Но даже для строк формата ISO 8601 некоторые реализации ошибаются. Вот сравнительный вывод 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, поэтому он не смещен от эпохи, и результат равен 0
- Во втором случае спецификатор «-0500» указывает, что ввод происходит в GMT-05: 00, и оба браузера интерпретируют ввод как находящийся в часовом поясе -05: 00. Это означает, что значение UTC смещено относительно эпохи, что означает добавление 18000000 мс к значению внутреннего времени даты.
- Третий случай, когда нет спецификатора, должен рассматриваться как локальный для хост-системы. FF правильно обрабатывает ввод как местное время, в то время как Chrome обрабатывает его как UTC, таким образом производя различные значения времени. Для меня это создает 5-часовую разницу в сохраненном значении, что проблематично. Другие системы с другим смещением получат разные результаты.
Это различие было исправлено с 2020 года, но существуют другие особенности браузеров при разборе строк в формате ISO 8601.
Но это становится хуже. Причудой ECMA-262 является то, что формат ISO 8601 только для даты (ГГГГ-ММ-ДД) должен быть проанализирован как UTC, тогда как ISO 8601 требует, чтобы он был проанализирован как локальный. Вот вывод из FF с длинными и короткими форматами даты ISO без указания часового пояса.
Date.parse('1970-01-01T00:00:00'); // 18000000
Date.parse('1970-01-01'); // 0
Поэтому первый анализируется как локальный, потому что это дата и время ISO 8601 без часового пояса, а второй - как UTC, потому что это только дата ISO 8601.
Таким образом, "YYYY-MM-DD"
ECMA-262 требует прямого ответа на исходный вопрос как UTC, а другой - как локальный. Поэтому:
Это не дает эквивалентных результатов:
console.log(new Date(Date.parse("Jul 8, 2005")).toString()); // Local
console.log(new Date(Date.parse("2005-07-08")).toString()); // UTC
Это делает:
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());
Большинство современных браузеров обрабатывают другие форматы ввода одинаково, включая часто используемые «1/1/1970» (M / D / YYYY) и «01.01.1970 00:00:00 AM» (M / D / YYYY чч : mm: ss ap) форматы. Все следующие форматы (кроме последнего) обрабатываются как ввод местного времени во всех браузерах. Вывод этого кода одинаков во всех браузерах в моем часовом поясе. Последний обрабатывается как -05: 00 независимо от часового пояса хоста, поскольку смещение установлено в метке времени:
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"));
Однако, поскольку синтаксический анализ даже форматов, указанных в ECMA-262, не является согласованным, рекомендуется никогда не полагаться на встроенный синтаксический анализатор и всегда анализировать строки вручную, например, используя библиотеку, и предоставлять формат анализатору.
Например, в moment.js вы можете написать:
let m = moment('1/1/1970', 'M/D/YYYY');
Выходная сторона
Что касается вывода, все браузеры переводят часовые пояса одинаково, но по-разному обрабатывают строковые форматы. Вот toString
функции и что они выводят. Обратите внимание на вывод функций toUTCString
и toISOString
5:00 на моей машине. Кроме того, имя часового пояса может быть аббревиатурой и может отличаться в разных реализациях.
Преобразует из UTC в Местное время перед печатью
- toString
- toDateString
- toTimeString
- toLocaleString
- toLocaleDateString
- toLocaleTimeString
Печатает сохраненное время UTC напрямую
- toUTCString
- toISOString
В Chrome
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
В Firefox
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"
Я надеюсь, что этот ответ был полезным.