Pull to refresh

ISO 8601 и ECMAScript — головная боль от разночтения стандартов

Reading time3 min
Views11K
Разрабатываем мы тут некоторый сервис интеграции с очень сторонней системой. Сам сервис работает на Node.js. И всё бы хорошо, но только недоступность сервера во время сборки мусора очень нервировала стороннюю систему.

В канун нового года было решено сделать серверу подарок — обновить Node.js с версии 0.4.8 до 0.6.6 В силу ряда организационных причин, обсуждать которые здесь не очень хочется, обновление было проведено сразу на боевой системе и даже без регрессионного тестирования.

Неужели в этой ситуации что-то могло пойти не так?

Обновились. Работаем дальше. Внезапно выясняется, что в передаваемых сторонней системой сообщениях время оказывается сдвинуто на 4 часа вперёд. О бизнес-последствиях такого сдвига рассказывать, наверное, не нужно.

Начинаем думать. Возникает гипотеза, что если всё раньше работало, а с обновлением Node.js вдруг перестало, то значит дело в нём или в v8. Не может быть, говорю. Чтобы такой косяк и мы первые заметили — наверняка это наш админ напортачил. Явно, говорю, у него что-то не так с установками часовой зоны на сервере. Заглянули во все возможные закоулки — нет, всё чисто.

Осталась последняя, самая невероятная гипотеза — сломался разбор времени в формате ISO 8601. Именно в этом формате сторонняя система и присылает время в сообщениях. Казалось бы, ну что тут можно сломать. Вот приходит локальное время: “2011-12-30T22:00:00”. По-быстрому смотрим в сервере:

> var d = new Date('2011-12-30T22:00:00')
undefined
> d
Fri, 30 Dec 2011 22:00:00 GMT
> d.getHours()
2

Опаньки. Сервер явно живёт по Гринвичу. Не иначе, уже эмигрировал. Проверяем:

> var d = new Date('2011-12-30T22:00:00Z')
undefined
> d
Fri, 30 Dec 2011 22:00:00 GMT
> d.getHours()
2

В обоих случаях, вне зависимости от того, указывается ли время локально или по Гринвичу, оно воспринимается как по Гринвичу. Смотрим, что на это скажет Хром:

> var d = new Date('2011-12-30T22:00:00')
undefined
> d.toString()
"Sat Dec 31 2011 02:00:00 GMT+0400 (MSK)"
> d.getHours()
2

А вот это уже неприятно. Хром — на рабочей станции. И с часовой зоной там всё в порядке.

Пока разработчик теребит меня вопросом “Когда уже баг на гугл постить будем?” я читаю описание стандарта. 130 франков на официальный документ нет, поэтому изучаем Википедию:
If no UTC relation information is given with a time representation, the time is assumed to be in local time

Копаем дальше.

var d = new Date('2011-12-30T22:00:00')
undefined
d. getTimezoneOffset()/60
-4

-4 возвращается на всех тестируемых компьютерах и платформах. Тут мне в голову приходит мысль. До сих пор мы проверяли везде кроме Firefox и Windows. Смотрю в FF под Windows:

var d = new Date('2011-12-30T22:00:00')
undefined
d.getUTCHours()
18
d.getHours()
22

Проверяю то же самое в Хроме под Windows:

var d = new Date('2011-12-30T22:00:00')
undefined
d.getUTCHours()
22
d.getHours()
2

Вот это да! Неужели в самом деле у гугла косяк? Снова поступает предложение написать баг-репорт. Сочинять его пока ещё лень, поэтому я начинаю читать руководства.

Mozilla Developer Network (MDN). Описание метода Date.parse:
If you do not specify a time zone, the local time zone is assumed

Здесь время разбирается по стандарту, поэтому у Firefox всё правильно.

На v8 самостоятельной документации нет, зато есть ссылка на ECMAscript. Скачиваем спецификацию версии 5.1, на странице 181 читаем:
The value of an absent time zone offset is «Z».

Ничего себе! Сторонняя система передаёт нам локальное время в соответствии со стандартом ISO, а наш сервер интерпретирует его как по Гринвичу — в полном соответствии со стандартом ECMA.

На всякий случай читаю MSDN — Microsoft ведь издавна утверждала, что их стандарт JavaScript самый правильный, потому что он ECMA. И точно:
If you do not include a value in the Z position, UTC time is used.

Изучение репозитариев показало, что то самое исправление поведения v8 было внесено в редакции 8513 в конце июня 2011. Однако, когда Мозилле было предложено исправить это у себя, в дискуссии было указано на разночтения в стандартах ISO и ECMA 5.1.В результате, есть шанс, что в версии 6 стандарта ECMA время будет таки разбираться правильно.

А нам пока пришлось добавить в код костылей. Если время, приходящее от сторонней системы, не содержат указания на часовую зону, добавляем к нему getTimezoneOffset. После того как выйдет новый стандарт ECMA, в котором это разночтение будет исправлено и v8 обновится в соответствии с этим стандартом, нам придется это отследить и костыли убрать.

P.S. Основной удар в описываемых событиях принял на себя хабраюзер zerodivisi0n, у которого, к сожалению, есть права только на чтение. Если у кого-то хватит кармы (или чего там необходимо), чтобы перевести его в более продвинутое состояние, мы оба будем весьма признательны. Заодно он сам и на вопросы сможет ответить.
Tags:
Hubs:
Total votes 58: ↑53 and ↓5+48
Comments37

Articles