Чому Date.parse дає неправильні результати?

Випадок перший:

Вихід:

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

дають

Випадок другий:

Вихід:

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

Чому другий розбір неправильний?

11 Відповіді 11

Поки не вийшла специфікація 5-го видання, метод Date.parse повністю залежав від реалізації (нова Date (рядок) еквівалентна Date.parse (рядок), за винятком того, що остання повертає число, а не Date). У специфікації 5-го видання була додана вимога щодо підтримки спрощеного (і дещо неправильного) ISO-8601 (також див. Які дійсні рядки часу та часу в JavaScript?). Але крім цього, не було вимоги щодо того, що Date.parse/new Date (рядок) повинен приймати, крім того, що вони повинні приймати будь-який вивід Date # toString (не кажучи, що це).

Починаючи з ECMAScript 2017 (випуск 8), реалізації були необхідні для синтаксичного аналізу своїх вихідних даних для Date # toString і Date # toUTCString, але формат цих рядків не був вказаний.

Станом на ECMAScript 2019 (видання 9) формат для Date # toString і Date # toUTCString були вказані як (відповідно):

  1. ddd MMM DD YYYY HH: mm: ss ZZ [(назва часового поясу)]
    напр. Вівторок, 10 липня 2018, 18:39:58 GMT + 0530 (IST)
  2. ddd, DD MMM РРРР HH: мм: ss Z
    напр. Вівторок, 10 липня 2018 р., 13:09:58 GMT

надання ще 2 форматів, які Date.parse повинен надійно аналізувати в нових реалізаціях (зазначивши, що підтримка не є всюдисущою, а невідповідні реалізації залишатимуться у користуванні деякий час).

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

Протягом недавнього досвіду написання перекладача JS я багато боровся з внутрішніми датами ECMA/JS. Отже, я вважаю, що я кину сюди свої 2 центи. Сподіваємось, обмін цими матеріалами допоможе іншим у будь-яких питаннях щодо відмінностей браузерів у тому, як вони обробляють дати.

Усі реалізації зберігають свої значення дати внутрішньо як 64-розрядні числа, які представляють кількість мілісекунд (мс) з 1970-01-01 UTC (GMT - це те саме, що UTC). Ця дата - епоха ECMAScript, яка також використовується іншими мовами, такими як системи Java та POSIX, такими як UNIX. Дати, що трапляються після епохи, є позитивними числами, а дати попередніми - негативними.

Наступний код інтерпретується як однакова дата у всіх поточних браузерах, але з локальним зміщенням часового поясу:

У моєму часовому поясі (EST, що становить -05: 00), результат дорівнює 18000000, тому що це стільки мс за 5 годин (це лише 4 години в перехід на літній час). Значення буде різним у різних часових поясах. Ця поведінка вказана в ECMA-262, тому всі браузери роблять це однаково.

Хоча існують певні розбіжності у форматах вхідних рядків, які основні браузери будуть аналізувати як дати, вони, по суті, інтерпретують їх однаково щодо часових поясів та переходу на літній час, хоча синтаксичний аналіз в основному залежить від реалізації.

Однак формат ISO 8601 відрізняється. Це один із лише двох форматів, окреслених у ECMAScript 2015 (видання 6), який повинен аналізуватися однаково для всіх реалізацій (інший - формат, вказаний для Date.prototype.toString).

Але навіть для рядків формату ISO 8601 деякі реалізації помиляються. Ось вихідний результат порівняння Chrome та Firefox, коли ця відповідь була спочатку написана на 1/1/1970 (епоха) на моїй машині з використанням рядків формату ISO 8601, які повинні бути проаналізовані з однаковим значенням у всіх реалізаціях:

  • У першому випадку специфікатор "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 без специфікатора часового поясу.

Отже, перший аналізується як локальний, оскільки це дата та час ISO 8601 без часового поясу, а другий аналізується як UTC, оскільки це лише дата ISO 8601.

Отже, щоб відповісти безпосередньо на вихідне запитання, ECMA-262 вимагає "РРРР-ММ-ДД" інтерпретувати як UTC, а інше - як місцеве. Ось чому:

Це не дає рівноцінних результатів:

Це робить:

Підсумок - це для аналізу рядків дати. ЄДИНИЙ рядок ISO 8601, який ви можете безпечно проаналізувати в браузерах, - це довга форма зі зміщенням (або ± HH: мм або "Z"). Якщо ви зробите це, ви можете безпечно переходити туди-сюди між місцевим часом і часом UTC.

Це працює в усіх браузерах (після IE9):

Більшість сучасних браузерів порівнюються з іншими форматами введення, включаючи часто використовувані "1/1/1970" (M/D/YYYY) та "1/1/1970 00:00:00 AM" (M/D/YYYY hh: mm: ss ap) формати. Усі наведені нижче формати (крім останнього) у всіх браузерах розглядаються як введення місцевого часу. Результат цього коду однаковий у всіх браузерах мого часового поясу. Останній трактується як -05: 00 незалежно від часового поясу хоста, оскільки зміщення встановлено в позначці часу:

Однак, оскільки синтаксичний аналіз навіть форматів, зазначених у ECMA-262, не узгоджується, рекомендується ніколи не покладатися на вбудований синтаксичний аналізатор і завжди аналізувати рядки вручну, скажімо, використовуючи бібліотеку та надаючи формат парсеру.

Наприклад у moment.js ви можете написати:

На виході всі браузери перекладають часові пояси однаково, але вони по-різному обробляють формати рядків. Ось функції toString і те, що вони виводять. Зверніть увагу на функції toUTCString та toISOString, що виводяться на моїй машині о 5:00 ранку. Крім того, назва часового поясу може бути абревіатурою і може відрізнятися в різних варіантах реалізації.

Перетворює з UTC на місцевий час перед друком

Друкує збережений час UTC безпосередньо

Зазвичай я не використовую формат ISO для введення рядків. Єдиний раз, коли використання цього формату мені вигідно, це коли дати потрібно сортувати як рядки. Формат ISO можна сортувати як є, тоді як інші - ні. Якщо вам потрібно мати сумісність між браузерами, або вкажіть часовий пояс, або використовуйте сумісний формат рядка.

Код new Date ('12/4/2013 '). ToString () проходить таке внутрішнє псевдоперетворення: