Chrome интерпретирует время ISO без Z как UTC; C# проблема
Запустите этот jsfiddle: http://jsfiddle.net/E9gq9/7/ в Chrome, FF и IE, и вы получите:
Хром:
http://images.devs-on.net/Image/vBTz86J0f4o8zlL3-Region.png
Fire Fox:
http://images.devs-on.net/Image/aNPxNPUpltyjVpSX-Region.png
IE:
http://images.devs-on.net/Image/WXLM5Ev1Viq4ecFq-Region.png
Сафари:
http://images.devs-on.net/Image/AEcyUouX04k2yIPo-Region.png
ISO 8601 не говорит, как следует интерпретировать строку без завершающего Z.
Наш сервер (ASP.NET MVC4) извлекает время UTC из нашей базы данных как DateTime
и просто вставляя их в JSON. Как вы можете видеть из-за этого, мы получаем противоречивые результаты в браузере.
Должны ли мы просто добавить Z к ним на стороне сервера?
4 ответа
Должны ли мы просто добавить Z к ним на стороне сервера?
TL; DR Да, вы, вероятно, должны.
К сожалению, правильная обработка дат и даты / времени без часового пояса менялась с годами - как с точки зрения спецификации, так и с точки зрения соблюдения JavaScript движками.
Когда этот ответ был изначально написан в 2013 году, спецификация ES5 (первая, в которой был определен формат даты / времени для JavaScript) была ясна: нет часового пояса = UTC:
Значение смещения отсутствующего часового пояса - "Z".
Это противоречило стандарту ISO-8601, на котором был основан формат даты / времени ES5, в котором отсутствие индикатора часового пояса означает "местное время". Некоторые реализации никогда не реализовывали значение ES5, вместо этого придерживаясь ISO-8601.
В ES2015 (он же "ES6") он был изменен в соответствии с ISO-8601:
Если смещение часового пояса отсутствует, дата-время интерпретируется как местное время.
Однако это вызвало проблемы несовместимости с существующим кодом, особенно с такими формами только для дат, как 2018-07-01
, поэтому в ES2016 это было изменено еще раз:
Когда смещение часового пояса отсутствует, формы только для даты интерпретируются как время UTC, а формы даты и времени интерпретируются как местное время.
Так new Date("2018-07-01")
анализируется как UTC, но new Date("2018-07-01T00")
анализируется как местное время
Это было последовательно с тех пор, как в ES2017 и в предстоящем ES2018; Вот ссылка на текущий проект стандарта, который также имеет текст выше.
Вы можете проверить свой текущий браузер здесь:
function test(val, expect) {
var result = +val === +expect ? "Good" : "ERROR";
console.log(val.toISOString(), expect.toISOString(), result);
}
test(new Date("2018-07-01"), new Date(Date.UTC(2018, 6, 1)));
test(new Date("2018-07-01T00:00:00"), new Date(2018, 6, 1));
Статус по состоянию на апрель 2018 года:
- IE11 понимает это правильно (интересно)
- Firefox понимает это правильно
- Chrome 65 (рабочий стол, Android) делает все правильно
- Chrome 64 (iOS v11.3) неправильно отображает форму даты / времени (анализируется как UTC)
- iOS Safari в версии 11.3 неверно отображает форму даты / времени (анализируется как UTC)
Как ни странно, я не могу найти проблему в списке проблем v8, которая была исправлена между v6.4 (v8 в Chrome 64) и v6.5 (v8 в Chrome 65); Я могу найти только эту проблему, которая все еще открыта, но которая, кажется, была исправлена.
В конце концов, проблема, с которой я сталкиваюсь в этом приложении, может быть исправлена, если мой сервер всегда отправляет клиентские объекты DateTime в формате, который корректно обрабатывается всеми браузерами.
Это означает, что на конце должно быть это "Z". Оказывается, что сериализатор Json ASP.NET MVC4 основан на Json.NET, но по умолчанию Utc не включен. По умолчанию для DateTimeZoneHandling
по-видимому RoundtripKind
и это не выводит значения с Z на них, даже если DateTime.Kind == Utc
, что довольно раздражает.
Таким образом, исправление выглядит так: установите способ, которым Json.NET обрабатывает часовые пояса: DateTimeZoneHandling.Utc
:
var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
// Force Utc formatting ('Z' at end).
json.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
Теперь все, что идет по проводам от моего сервера к браузеру, отформатировано как ISO-8601 с буквой "Z" в конце. И все браузеры, с которыми я тестировал, правильно делают с этим.
Я столкнулся с этой проблемой, и интерпретация даты с местным часовым поясом имела гораздо больший смысл, чем изменение на "Z", по крайней мере для моего приложения. Я создал эту функцию, чтобы добавить информацию о местном часовом поясе, когда она отсутствует в дате ISO. Это может быть использовано вместо новой даты (). Частично получен из этого ответа: Как ISO 8601 отформатировать дату со смещением часового пояса в JavaScript?
parseDate = function (/*String*/ d) {
if (d.search(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/) == 0) {
var pad = function (num) {
norm = Math.abs(Math.floor(num));
return (norm < 10 ? '0' : '') + norm;
},
tzo = -(new Date(d)).getTimezoneOffset(),
sign = tzo >= 0 ? '+' : '-';
return new Date(d + sign + pad(tzo / 60) + ':' + pad(tzo % 60));
} else {
return new Date(d);
}
}
В дополнение к ответу @tig (именно это я и искал):
Вот решение для.NetCore 1
services.AddMvc();
services.Configure<MvcJsonOptions>(o =>
{
o.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
});
или же
services.AddMvc().AddJsonOptions(o => o.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc);
Для.NetCore 1.0.1
services
.AddMvcCore()
.AddJsonFormatters(o => o...);
Ответ Дэвида Хаммонда великолепен, но он не играет всех хитростей; так вот модифицированная версия:
- допускает дробную часть в строке даты / времени
- допускает дополнительные секунды в строке даты / времени
- рассматривает переход на летнее время
appendTimezone = function (/*String*/ d) {
// check for ISO 8601 date-time string (seconds and fractional part are optional)
if (d.search(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1-3})?)?$/) == 0) {
var pad = function (num) {
norm = Math.abs(Math.floor(num));
return (norm < 10 ? '0' : '') + norm;
},
tzo = -new Date(d).getTimezoneOffset(),
sign = tzo >= 0 ? '+' : '-';
var adjusted = d + sign + pad(tzo / 60) + ':' + pad(tzo % 60);
// check whether timezone offsets are equal;
// if not then the specified date is just within the hour when the clock
// has been turned forward or back
if (-new Date(adjusted).getTimezoneOffset() != tzo) {
// re-adjust
tzo -= 60;
adjusted = d + sign + pad(tzo / 60) + ':' + pad(tzo % 60);
}
return adjusted;
} else {
return d;
}
}
parseDate = function (/*String*/ d) {
return new Date(appendTimezone(d));
}