Під час недавнього досвіду написання перекладача 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, коли ця відповідь спочатку була написана за 1.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 (YYYY-MM-DD), потрібно розбирати як 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, який ви можете безпечно проаналізувати у веб-переглядачах, - це довга форма зі зміщенням (або HH: мм, або "Z"). Якщо ви це зробите, ви можете сміливо повертатися вперед і назад між місцевим та UTC часом.
Це працює у веб-переглядачах (після IE9):
console.log(new Date(Date.parse("2005-07-08T00:00:00Z")).toString());
Більшість сучасних веб-переглядачів однаково ставляться до інших форматів введення, включаючи часто використовувані "1/1/1970" (M / D / РРРР) та "1/1/1970 00:00:00 AM" (M / D / РРРР-год. Год. : мм: сс ап) формати. Усі наведені нижче формати (крім останнього) розглядаються як локальний вхід часу у всіх браузерах. Вихід цього коду однаковий у всіх браузерах мого часового поясу. Останній трактується як -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"
Я сподіваюся, що ця відповідь була корисною.